diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 606dd4de42..36b31c0396 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -267,6 +267,9 @@ jobs: 'nativeSqlExcludesEveryForeignRemovedAndNonContainerRelationshipWithoutWrites', }, 'io.cattle.platform.iaas.api.auth.change.AiHostMetadataChangeTest': { + 'nativeDockerHostFullKeyUsesModelForVisiblePutAndReceipt', + 'nativeDockerHostReadKeyCanReadButRealWriteAuthorizationStillDenies', + 'hostSchemaRejectsWrongNativeModelEvenWhenItsKindClaimsHost', 'onlyMetadataIntentAndFieldsAreAccepted', 'nativePutUsesRealRevisionGuardAndRestoresContext', 'nativeMethodFieldAndCrossProjectCeilingsRejectBeforeMutation', @@ -304,6 +307,12 @@ jobs: 'emptyHandlersAndLegacyListOverrideRemainValidWithoutEagerDependencyCycles', 'nativeCreateBindsExactlyOnceAndNestedFailureRestoresThreadLocalEnvelope', }, + 'io.cattle.platform.iaas.api.auth.ai.catalog.AiCatalogLaunchDelegationTest': { + 'nativeVersionRootSchemaHandshakeIsRepeatableOwnerMetadataWithoutChildWrites', + 'versionRootAuditsVerifiedDenialsWithoutAttributingForgedProofToPlanOrKey', + 'versionRootRechecksEventProofOriginalOwnerSourceAndFrozenStackBeforeMetadata', + 'versionRootRejectsUnknownPathsMethodsQueriesAndContradictoryRouteShapesWithSafe403', + }, } suites = [] observed = {} diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 30f2d8a593..a377be8345 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -1,5 +1,30 @@ # Compatibility Contract +## Engine 0.183.371 source candidate + +The typed Host change owner uses the native Host model for visibility and +receipt checks. A Host can have driver kind `docker` while its API type remains +`host`; another native model cannot gain Host access by claiming that kind. +Service and Container kind checks, native read/write schema and Key ceilings, +approval, locking, durable claim and audit remain unchanged. No lifecycle or +permission grant is added. + +Three new focused Host cases cover Full-Key visibility/PUT/receipt, read-only +Key read versus write403, and a rejected wrong model. Four Catalog cases cover +the real API root/schema handshake, closed route shapes, fresh owner/source +checks and safe denial audit. Valid root/schema reads are repeatable metadata; +original delegated writes retain their event and replay boundaries. + +Both builders install the same already-pinned OpenSSL security packages before +the base upgrade. The temporary security source is removed and normal indexes +are reloaded before that base plan; its original source phase and final exact +OpenSSL readback are restored afterwards. The twelve already-pinned final +Python/Curl/CA/Brotli/PCRE packages enter through the normal local-deb APT solver +before the base plan. Only the three old bootstrap Python/Curl/CA version pins +are aligned with those existing final versions. Final package versions, security +lock, source URLs, SHA pins and snapshot dates remain unchanged. Official CI, +artifact publication and designated runtime acceptance remain pending. + ## Engine 0.183.370 published Host metadata is limited to name/description through the existing native PUT diff --git a/Dockerfile b/Dockerfile index fb77146e79..ff11d510ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY ubuntu-security-packages.lock /licenses/ubuntu-security-packages.lock COPY scripts/install-ubuntu-security-updates.sh /usr/local/bin/install-ubuntu-security-updates COPY scripts/install-python-security-tools.sh /usr/local/bin/install-python-security-tools COPY tools/pip-security-overlay /usr/local/lib/pip-security-overlay +COPY --chmod=0755 scripts/install-snapshot-packages /usr/local/bin/install-snapshot-packages ADD --checksum=sha256:fac6f0728cf9490a7a698e6b343e4140283e92355b060193c04f5770a666df8a https://security.ubuntu.com/ubuntu/pool/main/p/python3.14/libpython3.14-minimal_3.14.4-1ubuntu0.2_amd64.deb /tmp/libpython3.14-minimal.deb ADD --checksum=sha256:3c267ef0f902f763c3615cbf28dd6fe91d2e4f603075d7f03db99f8d4eb66f46 https://security.ubuntu.com/ubuntu/pool/main/p/python3.14/libpython3.14-stdlib_3.14.4-1ubuntu0.2_amd64.deb /tmp/libpython3.14-stdlib.deb ADD --checksum=sha256:0a7ea8a52834f9b3de0f11758924f80f7a6ebcbb4b02a55ba4f457503569f55f https://security.ubuntu.com/ubuntu/pool/main/p/python3.14/python3.14-minimal_3.14.4-1ubuntu0.2_amd64.deb /tmp/python3.14-minimal.deb @@ -79,9 +80,49 @@ RUN set -eux; \ printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute resolute-updates resolute-backports resolute-security\nComponents: main universe restricted multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ "${UBUNTU_APT_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-snapshot.sources; \ printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\nAcquire::http::Pipeline-Depth "0";\nAcquire::https::CaInfo "/etc/ssl/certs/ca-certificates.crt";\nAcquire::https::Verify-Peer "true";\nAcquire::https::Verify-Host "true";\nAcquire::AllowInsecureRepositories "false";\nAPT::Get::AllowUnauthenticated "false";\n' > /etc/apt/apt.conf.d/80pasturestack-retries; \ + printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ + "${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources; \ + apt-get update; \ + set --; \ + for package in libexpat1 libsqlite3-0 libssh2-1t64; do \ + test "$(grep -c "^${package}=" /licenses/ubuntu-security-packages.lock)" -eq 1; \ + entry=$(grep "^${package}=" /licenses/ubuntu-security-packages.lock); \ + set -- "$@" "$entry"; \ + done; \ + install-snapshot-packages install -y --no-install-recommends \ + libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}" "$@"; \ + for entry in "$@"; do \ + test "$(dpkg-query -W -f='${Version}' "${entry%%=*}")" = "${entry#*=}"; \ + done; \ + for package in libssl3t64 openssl openssl-provider-legacy; do \ + test "$(dpkg-query -W -f='${Version}' "${package}")" = "${UBUNTU_APT_OPENSSL_VERSION}"; \ + done; \ + rm -f /etc/apt/sources.list.d/pasturestack-security.sources; \ apt-get update; \ - apt-get upgrade -y; \ - apt-get install -y --no-install-recommends \ + set --; \ + for package in libpython3.14-minimal libpython3.14-stdlib python3.14-minimal python3.14 python3.14-venv curl libcurl3t64-gnutls libcurl4t64 ca-certificates libbrotli1 libpcre2-8-0 libpcre2-posix3; do \ + architecture=amd64; \ + case "$package" in \ + libpython3.14-minimal|libpython3.14-stdlib|python3.14-minimal|python3.14|python3.14-venv) version="${UBUNTU_APT_PYTHON3_14_VERSION}";; \ + curl|libcurl3t64-gnutls|libcurl4t64) version="${UBUNTU_APT_CURL_VERSION}";; \ + ca-certificates) version="${UBUNTU_APT_CA_CERTIFICATES_VERSION}"; architecture=all;; \ + libbrotli1) version=1.2.0-3ubuntu0.1;; \ + libpcre2-8-0|libpcre2-posix3) version=10.46-1ubuntu0.1;; \ + *) exit 1;; \ + esac; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Package)" = "$package"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Version)" = "$version"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Architecture)" = "$architecture"; \ + set -- "$@" "/tmp/${package}.deb"; \ + done; \ + install-snapshot-packages install -y --no-install-recommends "$@"; \ + for package in libpython3.14-minimal libpython3.14-stdlib python3.14-minimal python3.14 python3.14-venv curl libcurl3t64-gnutls libcurl4t64 ca-certificates libbrotli1 libpcre2-8-0 libpcre2-posix3; do \ + test "$(dpkg-query -W -f='${Version}' "$package")" = "$(dpkg-deb -f "/tmp/${package}.deb" Version)"; \ + done; \ + install-snapshot-packages upgrade -y; \ + install-snapshot-packages install -y --no-install-recommends \ bash="${UBUNTU_APT_BASH_VERSION}" \ ca-certificates="${UBUNTU_APT_CA_CERTIFICATES_VERSION}" \ curl="${UBUNTU_APT_CURL_BOOTSTRAP_VERSION}" \ @@ -104,10 +145,6 @@ RUN set -eux; \ printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ "${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources; \ apt-get update; \ - apt-get install -y --no-install-recommends \ - libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \ - openssl="${UBUNTU_APT_OPENSSL_VERSION}" \ - openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}"; \ for package in libssl3t64 openssl openssl-provider-legacy; do \ test "$(dpkg-query -W -f='${Version}' "${package}")" = "${UBUNTU_APT_OPENSSL_VERSION}"; \ done; \ diff --git a/Dockerfile.dapper b/Dockerfile.dapper index 4c361adb8d..79a49a440a 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -97,7 +97,47 @@ RUN set -eux; \ printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute resolute-updates resolute-backports resolute-security\nComponents: main universe\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ "${UBUNTU_APT_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-snapshot.sources; \ printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\nAcquire::http::Pipeline-Depth "0";\nAcquire::https::CaInfo "/etc/ssl/certs/ca-certificates.crt";\nAcquire::https::Verify-Peer "true";\nAcquire::https::Verify-Host "true";\nAcquire::AllowInsecureRepositories "false";\nAPT::Get::AllowUnauthenticated "false";\n' > /etc/apt/apt.conf.d/80pasturestack-retries; \ + printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ + "${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources; \ + apt-get update; \ + set --; \ + for package in libexpat1 libsqlite3-0 libssh2-1t64; do \ + test "$(grep -c "^${package}=" /licenses/ubuntu-security-packages.lock)" -eq 1; \ + entry=$(grep "^${package}=" /licenses/ubuntu-security-packages.lock); \ + set -- "$@" "$entry"; \ + done; \ + install-snapshot-packages install -y --no-install-recommends \ + libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}" "$@"; \ + for entry in "$@"; do \ + test "$(dpkg-query -W -f='${Version}' "${entry%%=*}")" = "${entry#*=}"; \ + done; \ + for package in libssl3t64 openssl openssl-provider-legacy; do \ + test "$(dpkg-query -W -f='${Version}' "${package}")" = "${UBUNTU_APT_OPENSSL_VERSION}"; \ + done; \ + rm -f /etc/apt/sources.list.d/pasturestack-security.sources; \ apt-get update; \ + set --; \ + for package in libpython3.14-minimal libpython3.14-stdlib python3.14-minimal python3.14 python3.14-venv curl libcurl3t64-gnutls libcurl4t64 ca-certificates libbrotli1 libpcre2-8-0 libpcre2-posix3; do \ + architecture=amd64; \ + case "$package" in \ + libpython3.14-minimal|libpython3.14-stdlib|python3.14-minimal|python3.14|python3.14-venv) version="${UBUNTU_APT_PYTHON3_14_VERSION}";; \ + curl|libcurl3t64-gnutls|libcurl4t64) version="${UBUNTU_APT_CURL_VERSION}";; \ + ca-certificates) version="${UBUNTU_APT_CA_CERTIFICATES_VERSION}"; architecture=all;; \ + libbrotli1) version=1.2.0-3ubuntu0.1;; \ + libpcre2-8-0|libpcre2-posix3) version=10.46-1ubuntu0.1;; \ + *) exit 1;; \ + esac; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Package)" = "$package"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Version)" = "$version"; \ + test "$(dpkg-deb -f "/tmp/${package}.deb" Architecture)" = "$architecture"; \ + set -- "$@" "/tmp/${package}.deb"; \ + done; \ + install-snapshot-packages install -y --no-install-recommends "$@"; \ + for package in libpython3.14-minimal libpython3.14-stdlib python3.14-minimal python3.14 python3.14-venv curl libcurl3t64-gnutls libcurl4t64 ca-certificates libbrotli1 libpcre2-8-0 libpcre2-posix3; do \ + test "$(dpkg-query -W -f='${Version}' "$package")" = "$(dpkg-deb -f "/tmp/${package}.deb" Version)"; \ + done; \ install-snapshot-packages upgrade -y; \ install-snapshot-packages install -y --no-install-recommends \ bash="${UBUNTU_APT_BASH_VERSION}" \ @@ -122,10 +162,6 @@ RUN set -eux; \ printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ "${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources; \ apt-get update; \ - install-snapshot-packages install -y --no-install-recommends \ - libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \ - openssl="${UBUNTU_APT_OPENSSL_VERSION}" \ - openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}"; \ for package in libssl3t64 openssl openssl-provider-legacy; do \ test "$(dpkg-query -W -f='${Version}' "${package}")" = "${UBUNTU_APT_OPENSSL_VERSION}"; \ done; \ diff --git a/README.md b/README.md index 6c09fc1c48..57cf56d198 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,19 @@ Rancher 1.6 ecosystem. It is not affiliated with Rancher Labs or SUSE. This fork of [`rancher/cattle`](https://github.com/rancher/cattle) preserves upstream history, authorship, licenses, and dependency notices. +## Source candidate: Engine 0.183.371 + +Typed Host metadata plans identify the native Host model independently of its +driver kind, including `docker`. This corrects Host preview and receipt reads +that could return 404 in 0.183.370. Native role/Key checks, human approval, +locking and exact-once dispatch remain unchanged. Qualification, publication +and native acceptance of this candidate are pending. The internal Catalog +launcher also gains its authenticated API root/schema metadata handshake; +resource grants and one-time writes remain bounded by the original delegation. +The builders install the existing pinned final security packages before the +frozen base plan, retaining the same final versions and source phases. See the +[candidate note](docs/releases/orchestration-engine-0.183.371.md). + ## Latest published release: Engine 0.183.370 [Engine 0.183.370](https://github.com/PastureStack/orchestration-engine/releases/tag/v0.183.370) diff --git a/code/framework/api-pub-sub-jetty/pom.xml b/code/framework/api-pub-sub-jetty/pom.xml index ca5a0d5a69..12c737806a 100644 --- a/code/framework/api-pub-sub-jetty/pom.xml +++ b/code/framework/api-pub-sub-jetty/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/api-pub-sub/pom.xml b/code/framework/api-pub-sub/pom.xml index e97f965522..7cd38434fd 100644 --- a/code/framework/api-pub-sub/pom.xml +++ b/code/framework/api-pub-sub/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/api/pom.xml b/code/framework/api/pom.xml index de7f78f7db..2ad250b951 100644 --- a/code/framework/api/pom.xml +++ b/code/framework/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/archaius/pom.xml b/code/framework/archaius/pom.xml index 69a015e5c3..eb44a73e08 100644 --- a/code/framework/archaius/pom.xml +++ b/code/framework/archaius/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.370 + 0.183.371 ../../meta-parent/pom.xml diff --git a/code/framework/async/pom.xml b/code/framework/async/pom.xml index 16be4ad8d2..115146baeb 100644 --- a/code/framework/async/pom.xml +++ b/code/framework/async/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/auditing/pom.xml b/code/framework/auditing/pom.xml index d28991f238..aa658c0ca5 100644 --- a/code/framework/auditing/pom.xml +++ b/code/framework/auditing/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/auditing/src/test/java/io/cattle/platform/iaas/api/auditing/ApiKeyAuditTest.java b/code/framework/auditing/src/test/java/io/cattle/platform/iaas/api/auditing/ApiKeyAuditTest.java index 4dc71c8dc6..ac9af67f44 100644 --- a/code/framework/auditing/src/test/java/io/cattle/platform/iaas/api/auditing/ApiKeyAuditTest.java +++ b/code/framework/auditing/src/test/java/io/cattle/platform/iaas/api/auditing/ApiKeyAuditTest.java @@ -145,6 +145,7 @@ public void governanceErrorsUseTheSamePublicKeyIdentityBeforeAndAfterValidation( service.idFormatter = typedFormatter(); ApiRequest request = new ApiRequest(null, null); request.setType(type); request.setId(id); request.setMethod("PUT"); request.setResponseCode(status); + request.setAttribute("apiKey.audit.requestId", "00001c99-0000-4000-8000-000000000000"); request.setRequestObject(Map.of("id", "1c99", "accountId", "1a99", "secretValue", "private-secret")); ApiContext.newContext().setPolicy(policy()); ApiContext.getContext().setIdFormatter(service.idFormatter); @@ -157,7 +158,10 @@ public void governanceErrorsUseTheSamePublicKeyIdentityBeforeAndAfterValidation( assertEquals("1a2", event.get("actor")); assertEquals("apiKey", event.get("targetType")); assertEquals("update", event.get("operation")); assertEquals(status, event.get("httpStatus")); assertEquals(status == 401 || status == 403 ? "DENIED" : "FAILED", event.get("outcome")); - assertFalse(delivered.toString().contains("private")); assertFalse(delivered.toString().contains("1c99")); + assertEquals("00001c99-0000-4000-8000-000000000000", event.get("requestId")); + assertFalse(event.containsValue("1c99")); + assertFalse(event.containsKey("requestObject")); assertFalse(event.containsKey("responseObject")); + assertFalse(event.containsKey("secretValue")); assertFalse(event.toString().contains("private-secret")); } } } diff --git a/code/framework/db-loader/pom.xml b/code/framework/db-loader/pom.xml index 3cb1bac916..aafe0604e8 100644 --- a/code/framework/db-loader/pom.xml +++ b/code/framework/db-loader/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/deferred/pom.xml b/code/framework/deferred/pom.xml index e774b792fe..fa49d2de04 100644 --- a/code/framework/deferred/pom.xml +++ b/code/framework/deferred/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/encryption/pom.xml b/code/framework/encryption/pom.xml index 85ab9bd9a9..a7dfdbe2e8 100644 --- a/code/framework/encryption/pom.xml +++ b/code/framework/encryption/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/engine/pom.xml b/code/framework/engine/pom.xml index 018b5c480d..736a2130fa 100644 --- a/code/framework/engine/pom.xml +++ b/code/framework/engine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/eventing/pom.xml b/code/framework/eventing/pom.xml index 6cc25af202..f9674ea542 100644 --- a/code/framework/eventing/pom.xml +++ b/code/framework/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/events/pom.xml b/code/framework/events/pom.xml index 6611570858..3dd958529c 100644 --- a/code/framework/events/pom.xml +++ b/code/framework/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/extension-spring/pom.xml b/code/framework/extension-spring/pom.xml index 64965df28e..1576cf2383 100644 --- a/code/framework/extension-spring/pom.xml +++ b/code/framework/extension-spring/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/extension/pom.xml b/code/framework/extension/pom.xml index fc55059518..3a68107f6d 100644 --- a/code/framework/extension/pom.xml +++ b/code/framework/extension/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/java-server/pom.xml b/code/framework/java-server/pom.xml index 1868e1655b..1506387aa0 100644 --- a/code/framework/java-server/pom.xml +++ b/code/framework/java-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/jmx/pom.xml b/code/framework/jmx/pom.xml index 90493aa6b7..2a2f2207e0 100644 --- a/code/framework/jmx/pom.xml +++ b/code/framework/jmx/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/jooq/pom.xml b/code/framework/jooq/pom.xml index 4d22214a24..f513d9d1a5 100644 --- a/code/framework/jooq/pom.xml +++ b/code/framework/jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/json/pom.xml b/code/framework/json/pom.xml index ac79a49611..ebede60ad6 100644 --- a/code/framework/json/pom.xml +++ b/code/framework/json/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/launcher/pom.xml b/code/framework/launcher/pom.xml index 8c0694f9db..e158c8fb20 100644 --- a/code/framework/launcher/pom.xml +++ b/code/framework/launcher/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/lock/pom.xml b/code/framework/lock/pom.xml index b2500aa5d4..b71a48cc3c 100644 --- a/code/framework/lock/pom.xml +++ b/code/framework/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/logback/pom.xml b/code/framework/logback/pom.xml index f21f68f7f0..3c10540049 100644 --- a/code/framework/logback/pom.xml +++ b/code/framework/logback/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.370 + 0.183.371 ../../meta-parent/pom.xml diff --git a/code/framework/managed-context/pom.xml b/code/framework/managed-context/pom.xml index 845af59bd4..3db20e4ca4 100644 --- a/code/framework/managed-context/pom.xml +++ b/code/framework/managed-context/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/metrics/pom.xml b/code/framework/metrics/pom.xml index 3ca8e59fee..ffb386932e 100644 --- a/code/framework/metrics/pom.xml +++ b/code/framework/metrics/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/module/pom.xml b/code/framework/module/pom.xml index 027bb42925..cf1a9d5fd7 100644 --- a/code/framework/module/pom.xml +++ b/code/framework/module/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/object/pom.xml b/code/framework/object/pom.xml index 6ffd58cbd6..1f91d3f943 100644 --- a/code/framework/object/pom.xml +++ b/code/framework/object/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/pool/pom.xml b/code/framework/pool/pom.xml index 8a56ad90ae..e12c4ea688 100644 --- a/code/framework/pool/pom.xml +++ b/code/framework/pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/resource-monitor/pom.xml b/code/framework/resource-monitor/pom.xml index e1251af43e..994a551d20 100644 --- a/code/framework/resource-monitor/pom.xml +++ b/code/framework/resource-monitor/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/schema/pom.xml b/code/framework/schema/pom.xml index 764f3fbba5..f092c8cea0 100644 --- a/code/framework/schema/pom.xml +++ b/code/framework/schema/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/server/pom.xml b/code/framework/server/pom.xml index 83948d75a9..02957bdf74 100644 --- a/code/framework/server/pom.xml +++ b/code/framework/server/pom.xml @@ -4,7 +4,7 @@ cattle-meta-parent io.cattle - 0.183.370 + 0.183.371 ../../meta-parent/pom.xml diff --git a/code/framework/spring/pom.xml b/code/framework/spring/pom.xml index 1721c6b2cd..83d2959f84 100644 --- a/code/framework/spring/pom.xml +++ b/code/framework/spring/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/system-task/pom.xml b/code/framework/system-task/pom.xml index d95516883f..70356d9ce2 100644 --- a/code/framework/system-task/pom.xml +++ b/code/framework/system-task/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/token/pom.xml b/code/framework/token/pom.xml index b6f496036d..fd7d3f14d7 100644 --- a/code/framework/token/pom.xml +++ b/code/framework/token/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/framework/utils/pom.xml b/code/framework/utils/pom.xml index c2aedff318..05da0b40db 100644 --- a/code/framework/utils/pom.xml +++ b/code/framework/utils/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/agent-instance/pom.xml b/code/iaas/agent-instance/pom.xml index 8c360e8430..b93fcb8bf2 100644 --- a/code/iaas/agent-instance/pom.xml +++ b/code/iaas/agent-instance/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/agent-server/pom.xml b/code/iaas/agent-server/pom.xml index 22e1b768cb..5683195985 100644 --- a/code/iaas/agent-server/pom.xml +++ b/code/iaas/agent-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/agent/pom.xml b/code/iaas/agent/pom.xml index 90b1147525..a03839be8e 100644 --- a/code/iaas/agent/pom.xml +++ b/code/iaas/agent/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/allocator/pom.xml b/code/iaas/allocator/pom.xml index d678223c6a..695a3df19e 100644 --- a/code/iaas/allocator/pom.xml +++ b/code/iaas/allocator/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/api-logic/pom.xml b/code/iaas/api-logic/pom.xml index 48c15b90c3..95451062da 100644 --- a/code/iaas/api-logic/pom.xml +++ b/code/iaas/api-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/archaius-management/pom.xml b/code/iaas/archaius-management/pom.xml index 0cd7129c8a..fd716d9292 100644 --- a/code/iaas/archaius-management/pom.xml +++ b/code/iaas/archaius-management/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/pom.xml b/code/iaas/auth-logic/pom.xml index 5995405f27..0051e99455 100644 --- a/code/iaas/auth-logic/pom.xml +++ b/code/iaas/auth-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java index 0acf2f4d76..8e8def9da6 100644 --- a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java +++ b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java @@ -54,7 +54,7 @@ public static boolean trustedDomain(ApiRequest request) { if (!sameId && permit.id() != null && request.getId() != null && request.getId().matches("[1-9][0-9]*") && ApiContext.getContext() != null && ApiContext.getContext().getIdFormatter() != null) sameId = request.getId().equals(ApiContext.getContext().getIdFormatter().parseId(permit.id())); - return permit.type().equals(request.getType()) && permit.method().equals(request.getMethod()) + return Objects.equals(permit.type(), request.getType()) && permit.method().equals(request.getMethod()) && sameId && Objects.equals(permit.action(), request.getAction()); } diff --git a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java index eacabfbcc2..cc52a4c0f2 100644 --- a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java +++ b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java @@ -15,6 +15,7 @@ import io.cattle.platform.object.util.ObjectUtils; import io.cattle.platform.process.common.handler.ProcessEventDecorator; import io.github.ibuildthecloud.gdapi.model.Resource; +import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import io.github.ibuildthecloud.gdapi.request.handler.ApiRequestCompletionSink; import jakarta.inject.Inject; @@ -32,6 +33,7 @@ /** One event-local, exact-graph capability for the authenticated internal Compose launcher. */ public class AiCatalogLaunchDelegation implements ProcessEventDecorator, ApiRequestCompletionSink, io.cattle.platform.api.auth.CatalogLaunchTransformAuthorization { + private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(AiCatalogLaunchDelegation.class); public static final String HEADER = "X-PastureStack-Catalog-Launch-Delegation"; private static final String VERIFIED = AiCatalogLaunchDelegation.class.getName(); private static final String CLAIM = VERIFIED + ".claim"; @@ -97,23 +99,37 @@ public Admission admit(ApiRequest request, Account authenticated) { if (launcher(authenticated) && targetsActiveExecution(request)) throw error(403, "CatalogLaunchDelegationRequired"); return null; } - if (!launcher(authenticated) || !proof.matches("cld1\\.[0-9a-f-]{36}\\.[A-Za-z0-9_-]{43}")) throw error(403, "CatalogLaunchDelegationDenied"); - String planId = "aicataloglaunch-" + proof.substring(5, 41); - return store.independent(() -> { - Map plan = store.loadForUpdate(planId); - if (plan == null) throw error(403, "CatalogLaunchDelegationDenied"); - if (!(plan.get("delegationBindingDigest") instanceof String bindingDigest) || !bindingDigest.matches("[0-9a-f]{64}") - || !constant(hash(proof + ":" + bindingDigest), Objects.toString(plan.get("delegationProofHash"), ""))) + try { + if (!launcher(authenticated) || !proof.matches("cld1\\.[0-9a-f-]{36}\\.[A-Za-z0-9_-]{43}")) throw error(403, "CatalogLaunchDelegationDenied"); - // The proof is authentic before any live-policy rejection. Only now - // may generic audit omit this private request/response payload. - ApiAuditPayload.verifiedCatalogLaunch(request); + String planId = "aicataloglaunch-" + proof.substring(5, 41); + return store.independent(() -> { + Map plan = store.loadForUpdate(planId); + if (plan == null) throw error(403, "CatalogLaunchDelegationDenied"); + if (!(plan.get("delegationBindingDigest") instanceof String bindingDigest) || !bindingDigest.matches("[0-9a-f]{64}") + || !constant(hash(proof + ":" + bindingDigest), Objects.toString(plan.get("delegationProofHash"), ""))) + throw error(403, "CatalogLaunchDelegationDenied"); + // The proof is authentic before any live-policy rejection. Only now + // may generic audit omit this private request/response payload. + ApiAuditPayload.verifiedCatalogLaunch(request); + return admitVerified(request, plan, bindingDigest); + }); + } catch (ClientVisibleException denied) { + // Unverified proofs have no trusted plan/Key attribution. Never log the proof, body or original error. + LOG.warn("Catalog launch delegation rejected: phase=admission status={} reason={}", denied.getStatus(), safeReason(denied)); + throw denied; + } + } + + private Admission admitVerified(ApiRequest request, Map plan, String bindingDigest) { + AiCatalogLaunchAccess.Authorization authority = null; + try { Map values = privateData.open(plan); if (!bindingDigest.equals(binding(plan, values))) throw error(403, "CatalogLaunchDelegationDenied"); requireActive(plan); if (!(plan.get("delegationExpiresAt") instanceof Number expiry) || clock.millis() >= expiry.longValue()) throw error(403, "CatalogLaunchDelegationExpired"); - var authority = access.bound(plan, request); access.catalog(authority); + authority = access.bound(plan, request); access.catalog(authority); frozenStack(plan, values); Object project = request.getAttribute(io.cattle.platform.core.constants.ProjectConstants.PROJECT_HEADER); String projectHeader = request.getServletContext().getRequest().getHeader(io.cattle.platform.core.constants.ProjectConstants.PROJECT_HEADER); @@ -134,12 +150,30 @@ public Admission admit(ApiRequest request, Account authenticated) { if (verified.operation() != null) claim(plan, verified, request); Account actor = access.objects.loadResource(Account.class, authority.current().policy().getAuthenticatedAsAccountId()); return new Admission(authority.current(), actor); - }); + } catch (ClientVisibleException denied) { + var current = authority; + if (current != null && current.key() != null) ApiKeyCredentialContext.attach(request, current.key()); + // A verified proof identifies this plan, but a rejected live owner must never be replaced by the launcher Policy. + AiCatalogLaunchAccess.inContext(request, current == null ? null : current.current().policy(), () -> { + plans.audit(request, "delegate", plan, false, safeReason(denied)); return null; + }); + throw denied; + } + } + + private static String safeReason(ClientVisibleException denied) { + String code = denied.getCode(); + return code != null && code.matches("[A-Za-z][A-Za-z0-9_]{0,63}") ? code : "CatalogLaunchDelegationDenied"; } Verified match(ApiRequest request, Map plan, Map values) { if (request.getLink() != null) throw error(403, "CatalogLaunchDelegationDenied"); String type = request.getType(), method = request.getMethod(), id = request.getId(), action = request.getAction(); + if (type == null) { + if ("GET".equals(method) && id == null && action == null && parameters(request).isEmpty() && versionRoot(request)) + return new Verified((String) plan.get("id"), null, null, null, true); + throw error(403, "CatalogLaunchDelegationDenied"); + } Map graph = map(values.get("graph")), created = AiCatalogLaunchMaterializer.nullableMap(plan.get("createdGraph")); String stackId = wire("stack", ((Number) plan.get("internalStackId")).longValue()); if ("GET".equals(method) && action == null) { @@ -193,6 +227,17 @@ && map(values.get("authorizedReferenceIds")).containsKey(params.get("name"))) throw error(403, "CatalogLaunchDelegationDenied"); } + private static boolean versionRoot(ApiRequest request) { + String version = request.getVersion(); + if (!("v1".equals(version) || "v2-beta".equals(version)) || request.getServletContext() == null) return false; + var http = request.getServletContext().getRequest(); + String path = http.getServletPath(), root = "/" + version; + return "GET".equals(http.getMethod()) && (root.equals(path) || (root + "/").equals(path)) + && Objects.equals(http.getRequestURI(), Objects.toString(http.getContextPath(), "") + path) + && http.getQueryString() == null && (request.getRequestObject() == null + || request.getRequestObject() instanceof Map body && body.isEmpty()); + } + void claim(Map plan, Verified verified, ApiRequest request) { Map ledger = AiCatalogLaunchMaterializer.nullableMap(plan.get("ledger")); String digest = graphs.digest(body(request)); diff --git a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java index 68613d3065..529c66e959 100644 --- a/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java +++ b/code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java @@ -112,12 +112,17 @@ private Object read(ApiRequest original, AiResourceChangeDescriptor descriptor, request.setAttribute(ALIAS_REFERENCE, new AliasReference(type, publicId, Long.toString(internalId(type, publicId)))); return authorized(original, request, () -> { Object row = manager(type).getById(type, request.getId(), new ListOptions(request)); - if (row == null || ObjectUtils.getRemoved(row) != null || !Objects.equals(ObjectUtils.getKind(row), type) + if (row == null || ObjectUtils.getRemoved(row) != null || !matchesResourceType(type, row) || !Objects.equals(ObjectUtils.getAccountId(row), ApiUtils.getPolicy().getAccountId())) throw error(404, "NotFound"); return row; }); } + private static boolean matchesResourceType(String type, Object target) { + // A Host's kind identifies its native driver (for example docker), not the API resource type. + return "host".equals(type) ? target instanceof Host : Objects.equals(ObjectUtils.getKind(target), type); + } + public Map prepare(ApiRequest original, AiResourceChangeDescriptor descriptor, String publicId, Map parameters) { return prepare(original, descriptor, publicId, parameters, null); @@ -406,7 +411,7 @@ public Object receiptTarget(ApiRequest request, AiResourceChangeDescriptor descr Schema schema = current.schemas().getSchema(descriptor.resourceType()); if (schema == null || schema.getResourceMethods() == null || !schema.getResourceMethods().contains("GET")) throw error(403, "PermissionDenied"); if (target == null || !Objects.equals(ObjectUtils.getAccountId(target), actor.getAccountId()) - || !Objects.equals(ObjectUtils.getKind(target), descriptor.resourceType()) || current.policy().authorizeObject(target) == null) + || !matchesResourceType(descriptor.resourceType(), target) || current.policy().authorizeObject(target) == null) throw error(403, "PermissionDenied"); if (descriptor.createsResource() && "service".equals(descriptor.resourceType()) && (number(ObjectUtils.getId(target)) != number(plan.get("internalResourceId")) diff --git a/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java b/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java index f79d9cbe0a..d4a180798c 100644 --- a/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java +++ b/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java @@ -31,6 +31,7 @@ import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; import io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl; import io.github.ibuildthecloud.gdapi.request.ApiRequest; +import io.github.ibuildthecloud.gdapi.request.parser.DefaultApiRequestParser; import io.github.ibuildthecloud.gdapi.server.model.ApiServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -59,6 +60,126 @@ public class AiCatalogLaunchDelegationTest { if (previous == null) ArchaiusUtil.getConfiguration().clearProperty("api.ai.catalog.launch.enabled"); else ArchaiusUtil.getConfiguration().setProperty("api.ai.catalog.launch.enabled", previous); } + @Test public void nativeVersionRootSchemaHandshakeIsRepeatableOwnerMetadataWithoutChildWrites() throws Exception { + for (String version : List.of("v1", "v2-beta")) { + Fixture f = new Fixture(); + for (String suffix : List.of("", "/schemas", "/", "/schemas")) { + ApiRequest request = f.route("/" + version + suffix, "GET", null, Map.of(), f.proof); + assertEquals(version, request.getVersion()); assertNull(request.getId()); assertNull(request.getLink()); assertNull(request.getAction()); + if (suffix.endsWith("schemas")) assertEquals("schema", request.getType()); else assertNull(request.getType()); + f.delegation.access.authenticator.handle(request); + assertTrue(AiCatalogLaunchDelegation.bootstrap(request)); + assertEquals(42L, ((Policy) ApiContext.getContext().getPolicy()).getAuthenticatedAsAccountId()); + assertEquals(5L, ((Policy) ApiContext.getContext().getPolicy()).getAccountId()); + assertTrue(AiCatalogLaunchAccess.trustedDomain(request)); + request.setUrlBuilder(mock(io.github.ibuildthecloud.gdapi.url.UrlBuilder.class)); + if (suffix.endsWith("schemas")) new io.github.ibuildthecloud.gdapi.request.handler.SchemasHandler().handle(request); + else new io.github.ibuildthecloud.gdapi.request.handler.VersionHandler().handle(request); + assertNotNull(request.getResponseObject()); + var locator = mock(io.github.ibuildthecloud.gdapi.request.resource.ResourceManagerLocator.class); + var resources = new io.github.ibuildthecloud.gdapi.request.handler.ResourceManagerRequestHandler(); + resources.setResourceManagerLocator(locator); resources.handle(request); verifyNoInteractions(locator); + assertFalse(new JacksonJsonMapper().writeValueAsString(request.getResponseObject()).contains(f.proof)); + f.delegation.complete(request, null); assertEquals(200, request.getResponseCode()); + } + ApiRequest target = f.route("/" + version + "/stacks/1st889", "GET", null, Map.of(), f.proof); + f.delegation.access.authenticator.handle(target); + assertFalse(AiCatalogLaunchDelegation.bootstrap(target)); + assertEquals(0, f.store.writesAfterIssue); assertFalse(f.plan.containsKey("ledger")); assertFalse(f.plan.containsKey("createdGraph")); + verify(f.delegation.plans, times(5)).audit(any(), eq("delegate"), same(f.plan), eq(true), eq("CatalogLaunchDelegationAllowed")); + verifyNoInteractions(f.transformer); + } + } + + @Test public void versionRootRejectsUnknownPathsMethodsQueriesAndContradictoryRouteShapesWithSafe403() throws Exception { + for (String mutation : List.of("empty", "slash", "unknown", "unsupported", "post", "query", "action", "link", "id", "uri", + "null-unknown", "version", "body", "params")) { + Fixture f = new Fixture(); + String path = switch (mutation) { + case "empty" -> ""; case "slash" -> "/"; case "unknown", "null-unknown" -> "/v2-beta/unknown"; + case "unsupported" -> "/v9"; default -> "/v2-beta"; + }; + ApiRequest request = f.route(path, "post".equals(mutation) ? "POST" : "GET", + "query".equals(mutation) ? "name=private-synthetic" : null, + "query".equals(mutation) ? Map.of("name", new String[] { "private-synthetic" }) : Map.of(), f.proof); + if ("action".equals(mutation)) request.setAction("activate"); + if ("link".equals(mutation)) request.setLink("services"); + if ("id".equals(mutation)) request.setId("1st889"); + if ("uri".equals(mutation)) when(request.getServletContext().getRequest().getRequestURI()).thenReturn("/v2-beta/unknown"); + if ("null-unknown".equals(mutation)) request.setType(null); + if ("version".equals(mutation)) request.setVersion("v1"); + if ("body".equals(mutation)) request.setRequestObject(Map.of("name", "private-synthetic")); + if ("params".equals(mutation)) request.setRequestParams(Map.of("name", "private-synthetic")); + var denied = assertThrows(mutation, ClientVisibleException.class, () -> f.delegation.access.authenticator.handle(request)); + assertEquals(mutation, 403, denied.getStatus()); assertEquals("CatalogLaunchDelegationDenied", denied.getCode()); + assertFalse(AiCatalogLaunchDelegation.bootstrap(request)); assertEquals(0, f.store.writesAfterIssue); + verifyNoInteractions(f.transformer); + } + } + + @Test public void versionRootRechecksEventProofOriginalOwnerSourceAndFrozenStackBeforeMetadata() throws Exception { + for (String mutation : List.of("forged", "event", "expired", "closed", "project", "owner", "revision", "revoked", + "credential-expiry", "policy", "source", "stack")) { + Fixture f = new Fixture(); String proof = f.proof; + if ("forged".equals(mutation)) proof = proof.substring(0, proof.length() - 1) + (proof.endsWith("A") ? "B" : "A"); + if ("event".equals(mutation)) f.plan.put("delegationEventId", UUID.randomUUID().toString()); + if ("expired".equals(mutation)) f.plan.put("delegationExpiresAt", 0L); + if ("closed".equals(mutation)) f.plan.put("delegationClosed", true); + if ("owner".equals(mutation)) f.denial = "OwnerPermissionDenied"; + if ("revision".equals(mutation)) f.denial = "ApiKeyPolicyChanged"; + if ("revoked".equals(mutation)) f.denial = "ApiKeyRevoked"; + if ("credential-expiry".equals(mutation)) f.denial = "ApiKeyExpired"; + if ("policy".equals(mutation)) f.denial = "KeyPolicyDenied"; + if ("source".equals(mutation)) f.delegation.gateway = new AiCatalogLaunchGateway() { + @Override public Map material(String p, String t, String v) { + return Map.of("digest", "b".repeat(64), "questionSchema", List.of()); + } + }; + if ("stack".equals(mutation)) f.stack.setName("changed"); + ApiRequest request = f.route("/v2-beta", "GET", null, Map.of(), proof); + if ("project".equals(mutation)) request.setAttribute(io.cattle.platform.core.constants.ProjectConstants.PROJECT_HEADER, "1a88"); + var denied = assertThrows(mutation, ClientVisibleException.class, () -> f.delegation.access.authenticator.handle(request)); + String expected = switch (mutation) { + case "expired" -> "CatalogLaunchDelegationExpired"; case "owner" -> "OwnerPermissionDenied"; + case "revision" -> "ApiKeyPolicyChanged"; case "source", "stack" -> "CatalogLaunchSourceChanged"; + case "revoked" -> "ApiKeyRevoked"; case "credential-expiry" -> "ApiKeyExpired"; case "policy" -> "KeyPolicyDenied"; + default -> "CatalogLaunchDelegationDenied"; + }; + assertEquals(mutation, Set.of("source", "stack").contains(mutation) ? 409 : 403, denied.getStatus()); + assertEquals(mutation, expected, denied.getCode()); + assertFalse(AiCatalogLaunchDelegation.bootstrap(request)); assertEquals(0, f.store.writesAfterIssue); + verifyNoInteractions(f.transformer); + } + } + + @Test public void versionRootAuditsVerifiedDenialsWithoutAttributingForgedProofToPlanOrKey() throws Exception { + for (String mutation : List.of("query", "expired", "owner", "forged")) { + Fixture f = new Fixture(); String proof = f.proof; + if ("expired".equals(mutation)) f.plan.put("delegationExpiresAt", 0L); + if ("owner".equals(mutation)) f.denial = "OwnerPermissionDenied"; + if ("forged".equals(mutation)) proof = proof.substring(0, proof.length() - 1) + (proof.endsWith("A") ? "B" : "A"); + ApiRequest request = f.route("/v2-beta", "GET", "query".equals(mutation) ? "synthetic-private=secret" : null, + "query".equals(mutation) ? Map.of("synthetic-private", new String[] { "secret" }) : Map.of(), proof); + ClientVisibleException denied = assertThrows(ClientVisibleException.class, () -> f.delegation.access.authenticator.handle(request)); + assertEquals(403, denied.getStatus()); + assertEquals("expired".equals(mutation) ? "CatalogLaunchDelegationExpired" + : "owner".equals(mutation) ? "OwnerPermissionDenied" : "CatalogLaunchDelegationDenied", denied.getCode()); + if ("forged".equals(mutation)) { + assertTrue(f.catalogDecisions.isEmpty()); assertNull(ApiKeyCredentialContext.get(request)); + } else { + assertEquals(1, f.catalogDecisions.size()); Map decision = f.catalogDecisions.getFirst(); + assertEquals(false, decision.get("allowed")); assertEquals(denied.getCode(), decision.get("reason")); + assertEquals(f.plan.get("id"), decision.get("planId")); + assertEquals("query".equals(mutation) ? 42L : null, decision.get("actor")); + assertEquals("query".equals(mutation) ? 7L : null, decision.get("key")); + assertFalse(decision.toString().contains(proof)); assertFalse(decision.toString().contains("synthetic-private")); + assertFalse(decision.toString().contains("secret")); + } + assertNull(ApiContext.getContext().getPolicy()); assertSame(request, ApiContext.getContext().getApiRequest()); + assertEquals(0, f.store.writesAfterIssue); verifyNoInteractions(f.transformer); + } + } + @Test public void earlyTransformUsesRealAuthenticationAndFrozenInspectInsteadOfLauncherFullPolicy() throws Exception { Fixture f = new Fixture(); ApiRequest request = f.transform(f.proof, f.inspect); assertTrue(f.handler.handle(request)); @@ -231,11 +352,14 @@ final class Fixture { final TransformInspect handler = new TransformInspect(); final DockerTransformer transformer = mock(DockerTransformer.class); final Map inspect = Map.of("Name", "/app", "Config", Map.of("Image", "approved")); final Map plan = new LinkedHashMap<>(), values = new LinkedHashMap<>(); + final List> catalogDecisions = new java.util.ArrayList<>(); final StackRecord stack = new StackRecord(); final String proof; Fixture() throws Exception { this(Map.of()); } Fixture(Map authorizedReferenceIds) throws Exception { var json = new JacksonJsonMapper(); graphs.json = json; SchemaImpl script = new SchemaImpl(); script.setId("scripts"); when(schemas.getSchema("scripts")).thenReturn(script); + when(schemas.getSingularName("schemas")).thenReturn("schema"); when(schemas.getSingularName("stacks")).thenReturn("stack"); + when(schemas.typeStringMatches(io.github.ibuildthecloud.gdapi.model.Schema.class, "schema")).thenReturn(true); DefaultIdFormatter formatter = new DefaultIdFormatter(); formatter.setSchemaFactory(schemas); formatter.setTypeMappings(Map.of("account", "a", "project", "a", "stack", "st", "service", "s")); stack.setId(889L); stack.setAccountId(5L); stack.setName("chosen"); stack.setKind("stack"); stack.setState("active"); @@ -284,6 +408,7 @@ final class Fixture { when(authDao.hasAccessToProject(anyLong(), anyLong(), anyBoolean(), anySet())).thenReturn(true); inject(authenticator, "authDao", authDao); inject(authenticator, "accountDao", accounts); inject(authenticator, "objectManager", objects); + inject(authenticator, "auditSinks", List.of(mock(ApiKeyAuditSink.class))); inject(authenticator, "catalogLaunchDelegation", delegation); inject(authenticator, "apiKeyAuthorization", keyAccess); access.authenticator = authenticator; delegation.access = access; delegation.store = store; delegation.privateData = privateData; delegation.graphs = graphs; @@ -291,6 +416,15 @@ final class Fixture { return Map.of("digest", "a".repeat(64), "questionSchema", List.of()); } }; delegation.plans = mock(AiCatalogLaunchPlanService.class); + doAnswer(call -> { + Policy auditOwner = (Policy) ApiContext.getContext().getPolicy(); + ApiKeyCredentialContext auditKey = ApiKeyCredentialContext.get(call.getArgument(0)); + Map decision = new LinkedHashMap<>(); + decision.put("allowed", call.getArgument(3)); decision.put("reason", call.getArgument(4)); + decision.put("planId", ((Map) call.getArgument(2)).get("id")); + decision.put("actor", auditOwner == null ? null : auditOwner.getAuthenticatedAsAccountId()); + decision.put("key", auditKey == null ? null : auditKey.credentialId()); catalogDecisions.add(decision); return null; + }).when(delegation.plans).audit(any(), eq("delegate"), anyMap(), anyBoolean(), anyString()); ProcessState state = mock(ProcessState.class); when(state.getResource()).thenReturn(stack); ProcessInstance process = mock(ProcessInstance.class); when(process.getName()).thenReturn("stack.create"); EventVO event = EventVO.newEvent("stack.create;handler=compose-executor"); delegation.decorate(state, process, event); @@ -313,6 +447,19 @@ ApiRequest domain(String type, String method, String id, String action, Map parameters, String token) throws Exception { + ApiServletContext servlet = mock(ApiServletContext.class); HttpServletRequest http = mock(HttpServletRequest.class); + when(servlet.getRequest()).thenReturn(http); when(servlet.getResponse()).thenReturn(mock(HttpServletResponse.class)); + when(http.getHeader(AiCatalogLaunchDelegation.HEADER)).thenReturn(token); when(http.getMethod()).thenReturn(method); + when(http.getServletPath()).thenReturn(path); when(http.getRequestURI()).thenReturn(path); when(http.getContextPath()).thenReturn(""); + when(http.getRequestURL()).thenReturn(new StringBuffer("http://127.0.0.1:8080" + path)); + when(http.getParameterMap()).thenReturn(parameters); when(http.getQueryString()).thenReturn(query); + when(http.getLocale()).thenReturn(java.util.Locale.ENGLISH); + ApiRequest request = new ApiRequest(servlet, schemas); DefaultApiRequestParser parser = new DefaultApiRequestParser(); + parser.init(); assertTrue(parser.parse(request)); + new io.github.ibuildthecloud.gdapi.request.handler.BodyParserRequestHandler().handle(request); + ApiContext.getContext().setPolicy(null); ApiContext.getContext().setApiRequest(request); return request; + } } private static AccountRecord account(long id, String kind, String uuid) { diff --git a/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChangeTest.java b/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChangeTest.java index 5df0eaa2b2..813dd02827 100644 --- a/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChangeTest.java +++ b/code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChangeTest.java @@ -7,8 +7,14 @@ import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.core.model.tables.records.HostRecord; +import io.cattle.platform.core.model.tables.records.ServiceRecord; import io.cattle.platform.iaas.api.auth.apikey.ApiKeyAiBridgeGuard; import io.cattle.platform.iaas.api.auth.apikey.ApiKeyAuthorizationService; +import io.cattle.platform.iaas.api.auth.apikey.ApiKeyCredentialContext; +import io.cattle.platform.iaas.api.auth.apikey.ApiKeyPolicy; +import io.cattle.platform.iaas.api.auth.apikey.ApiKeyPolicyEvaluator; +import io.cattle.platform.iaas.api.auth.apikey.ApiKeyTargetResolver; +import io.cattle.platform.api.auth.ApiKeyAuditSink; import io.cattle.platform.api.auth.Policy; import io.cattle.platform.json.JacksonJsonMapper; import io.cattle.platform.object.util.DataAccessor; @@ -22,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.sql.DriverManager; import org.jooq.SQLDialect; import org.jooq.impl.DSL; @@ -90,10 +97,13 @@ public class AiHostMetadataChangeTest { row.setAccountId(88L); assertEquals("NotFound", assertThrows(ClientVisibleException.class, () -> f.domain.visible(f.request, descriptor(), "1h7")).getCode()); - row.setAccountId(77L); row.setKind("service"); + row.setAccountId(77L); + var wrongModel = new ServiceRecord(); wrongModel.setId(7L); wrongModel.setAccountId(77L); wrongModel.setKind("host"); + when(manager.getById(eq("host"), eq("1h7"), any())).thenReturn(wrongModel); assertEquals("NotFound", assertThrows(ClientVisibleException.class, () -> f.domain.visible(f.request, descriptor(), "1h7")).getCode()); - row.setKind("host"); assertSame(row, f.domain.visible(f.request, descriptor(), "1h7")); + when(manager.getById(eq("host"), eq("1h7"), any())).thenReturn(row); + assertSame(row, f.domain.visible(f.request, descriptor(), "1h7")); f.domain.keyAccess = new ApiKeyAuthorizationService() { @Override public void authorize(ApiRequest request, Policy policy) { throw new ClientVisibleException(403, "KeyScopeDenied"); @@ -109,6 +119,69 @@ public class AiHostMetadataChangeTest { } } + @Test public void nativeDockerHostFullKeyUsesModelForVisiblePutAndReceipt() throws Exception { + Object previous = ArchaiusUtil.getConfiguration().getProperty("api.ai.bridge.enabled"); + try { + ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", true); + var f = fixture(); var row = host(); var manager = f.domain.managers.getResourceManagerByType("host"); + nativeKeyAccess(f, new ApiKeyPolicy(ApiKeyPolicy.Mode.FULL, ApiKeyPolicy.Effect.ALLOW, null, + List.of(), ApiKeyPolicy.Purpose.AI_BRIDGE)); + when(manager.getById(eq("host"), eq("1h7"), any())).thenReturn(row); + when(manager.update(eq("host"), eq("1h7"), any())).thenReturn(row); + when(f.domain.objects.loadResource("host", 7L)).thenReturn(row); + when(f.policy.authorizeObject(row)).thenReturn(row); + assertEquals("docker", row.getKind()); + assertSame(row, f.domain.visible(f.request, descriptor(), "1h7")); + assertEquals(Map.of("name", "after"), f.domain.prepare(f.request, descriptor(), "1h7", Map.of("name", "after"))); + assertSame(row, f.domain.dispatch(f.request, descriptor(), "1h7", Map.of("name", "after"), "own-plan")); + assertSame(row, f.domain.receiptTarget(f.request, descriptor(), Map.of("internalResourceId", 7L, "resourceId", "1h7"))); + verify(manager, times(1)).update(eq("host"), eq("1h7"), any()); + assertSame(f.request, ApiContext.getContext().getApiRequest()); assertSame(f.policy, ApiContext.getContext().getPolicy()); + } finally { + if (previous == null) ArchaiusUtil.getConfiguration().clearProperty("api.ai.bridge.enabled"); + else ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", previous); + } + } + + @Test public void nativeDockerHostReadKeyCanReadButRealWriteAuthorizationStillDenies() throws Exception { + Object previous = ArchaiusUtil.getConfiguration().getProperty("api.ai.bridge.enabled"); + try { + ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", true); + var f = fixture(); var row = host(); var manager = f.domain.managers.getResourceManagerByType("host"); + nativeKeyAccess(f, new ApiKeyPolicy(ApiKeyPolicy.Mode.CUSTOM, ApiKeyPolicy.Effect.DENY, null, + List.of(new ApiKeyPolicy.Rule("read-host-project", ApiKeyPolicy.Effect.ALLOW, + ApiKeyPolicy.Scope.project("1a77"), Set.of("read"))), ApiKeyPolicy.Purpose.AI_BRIDGE)); + when(manager.getById(eq("host"), eq("1h7"), any())).thenReturn(row); + assertSame(row, f.domain.visible(f.request, descriptor(), "1h7")); + var denied = assertThrows(ClientVisibleException.class, + () -> f.domain.prepare(f.request, descriptor(), "1h7", Map.of("name", "never-written"))); + assertEquals(403, denied.getStatus()); assertEquals("KeyScopeDenied", denied.getCode()); + assertEquals("before", row.getName()); verify(manager, never()).update(anyString(), anyString(), any()); + assertSame(f.request, ApiContext.getContext().getApiRequest()); assertSame(f.policy, ApiContext.getContext().getPolicy()); + } finally { + if (previous == null) ArchaiusUtil.getConfiguration().clearProperty("api.ai.bridge.enabled"); + else ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", previous); + } + } + + @Test public void hostSchemaRejectsWrongNativeModelEvenWhenItsKindClaimsHost() throws Exception { + Object previous = ArchaiusUtil.getConfiguration().getProperty("api.ai.bridge.enabled"); + try { + ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", true); + var f = fixture(); var manager = f.domain.managers.getResourceManagerByType("host"); + nativeKeyAccess(f, new ApiKeyPolicy(ApiKeyPolicy.Mode.FULL, ApiKeyPolicy.Effect.ALLOW, null, + List.of(), ApiKeyPolicy.Purpose.AI_BRIDGE)); + var wrong = new ServiceRecord(); wrong.setId(7L); wrong.setAccountId(77L); wrong.setKind("host"); + when(manager.getById(eq("host"), eq("1h7"), any())).thenReturn(wrong); + var denied = assertThrows(ClientVisibleException.class, () -> f.domain.visible(f.request, descriptor(), "1h7")); + assertEquals(404, denied.getStatus()); assertEquals("NotFound", denied.getCode()); + verify(manager, never()).update(anyString(), anyString(), any()); + } finally { + if (previous == null) ArchaiusUtil.getConfiguration().clearProperty("api.ai.bridge.enabled"); + else ArchaiusUtil.getConfiguration().setProperty("api.ai.bridge.enabled", previous); + } + } + @Test public void durableSnapshotAndSummaryExcludeMaterialAndObserveExactMetadata() { var f = fixture(); var row = host(); for (String field : List.of("credentials", "secretKey", "agentToken")) DataAccessor.fields(row).withKey(field).set("PRIVATE-CANARY"); @@ -150,7 +223,7 @@ public class AiHostMetadataChangeTest { var sql = DSL.using(configuration); sql.createSchemaIfNotExists("cattle").execute(); sql.createTable(HOST).columns(HOST.fields()).execute(); sql.insertInto(HOST).set(HOST.ID, 7L).set(HOST.ACCOUNT_ID, 77L).set(HOST.UUID, "host-uuid") - .set(HOST.KIND, "host").set(HOST.NAME, "before") + .set(HOST.KIND, "docker").set(HOST.NAME, "before") .set(HOST.STATE, "active").set(HOST.IS_PUBLIC, false).set(HOST.DATA, Map.of()).execute(); var domain = new AiResourceChangeDomain(); domain.json = new JacksonJsonMapper(); domain.setConfiguration(configuration); assertEquals("ResourceChangeTransactionRequired", assertThrows(ClientVisibleException.class, @@ -168,7 +241,7 @@ public class AiHostMetadataChangeTest { private static HostRecord host() { var row = new HostRecord(); row.setId(7L); row.setAccountId(77L); row.setUuid("host-uuid"); - row.setKind("host"); row.setName("before"); DataAccessor.fields(row).withKey("hostname").set("node.example.test"); + row.setKind("docker"); row.setName("before"); DataAccessor.fields(row).withKey("hostname").set("node.example.test"); row.setState("active"); return row; } @@ -181,6 +254,16 @@ private static AiResourceChangeDomainTest.Fixture fixture() { when(f.domain.managers.getResourceManagerByType("host")).thenReturn(mock(ResourceManager.class)); return f; } + private static void nativeKeyAccess(AiResourceChangeDomainTest.Fixture f, ApiKeyPolicy policy) throws Exception { + var access = new ApiKeyAuthorizationService(); var targets = mock(ApiKeyTargetResolver.class); + when(targets.resolve(any(), any())).thenReturn(List.of(ApiKeyPolicyEvaluator.Target.projectResource("host", "1h7", "1a77"))); + for (var field : Map.of("targets", targets, "auditSinks", List.of(mock(ApiKeyAuditSink.class))).entrySet()) { + var member = ApiKeyAuthorizationService.class.getDeclaredField(field.getKey()); member.setAccessible(true); member.set(access, field.getValue()); + } + f.domain.keyAccess = access; + ApiKeyCredentialContext.attach(f.request, new ApiKeyCredentialContext(7, 42, "apiKeyRestricted", 3, policy)); + } + private static AiResourceChangePlanServiceTest.Fixture preview() { var f = new AiResourceChangePlanServiceTest.Fixture(); var row = host(); var snapshotOwner = new AiResourceChangeDomain(); snapshotOwner.json = f.json; diff --git a/code/iaas/bootstrap/pom.xml b/code/iaas/bootstrap/pom.xml index f0fca3432b..f8db36f315 100644 --- a/code/iaas/bootstrap/pom.xml +++ b/code/iaas/bootstrap/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/config-item/api/pom.xml b/code/iaas/config-item/api/pom.xml index e892aaa4ea..a4211eb619 100644 --- a/code/iaas/config-item/api/pom.xml +++ b/code/iaas/config-item/api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/iaas/config-item/common/pom.xml b/code/iaas/config-item/common/pom.xml index bdd8b9e7b0..db465f7a9a 100644 --- a/code/iaas/config-item/common/pom.xml +++ b/code/iaas/config-item/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/iaas/config-item/server/pom.xml b/code/iaas/config-item/server/pom.xml index a066b00c72..e8182734d4 100644 --- a/code/iaas/config-item/server/pom.xml +++ b/code/iaas/config-item/server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/iaas/engine-jooq/pom.xml b/code/iaas/engine-jooq/pom.xml index b7930ec407..8970c6edde 100644 --- a/code/iaas/engine-jooq/pom.xml +++ b/code/iaas/engine-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/events/pom.xml b/code/iaas/events/pom.xml index cfb262d1cb..e5eacf569b 100644 --- a/code/iaas/events/pom.xml +++ b/code/iaas/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/external-handler/pom.xml b/code/iaas/external-handler/pom.xml index a2549f7485..92e6966c67 100644 --- a/code/iaas/external-handler/pom.xml +++ b/code/iaas/external-handler/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/ha/pom.xml b/code/iaas/ha/pom.xml index 721c00d20b..bfe4bbca7c 100644 --- a/code/iaas/ha/pom.xml +++ b/code/iaas/ha/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/healthcheck/pom.xml b/code/iaas/healthcheck/pom.xml index 1a4e53b690..fd181f1b8c 100644 --- a/code/iaas/healthcheck/pom.xml +++ b/code/iaas/healthcheck/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/labels/pom.xml b/code/iaas/labels/pom.xml index bdd65bc54c..c12cc97e91 100644 --- a/code/iaas/labels/pom.xml +++ b/code/iaas/labels/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/logic-common/pom.xml b/code/iaas/logic-common/pom.xml index 3b2e17d3d5..2d4dc63df1 100644 --- a/code/iaas/logic-common/pom.xml +++ b/code/iaas/logic-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/logic/pom.xml b/code/iaas/logic/pom.xml index 8a2c854e47..0bc214dbc9 100644 --- a/code/iaas/logic/pom.xml +++ b/code/iaas/logic/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/metadata/pom.xml b/code/iaas/metadata/pom.xml index 711d08006f..f6682518fa 100644 --- a/code/iaas/metadata/pom.xml +++ b/code/iaas/metadata/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/model/pom.xml b/code/iaas/model/pom.xml index b1e117fadc..aa65447d2d 100644 --- a/code/iaas/model/pom.xml +++ b/code/iaas/model/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/resource-pool/pom.xml b/code/iaas/resource-pool/pom.xml index 65cbba222c..dc047c2d0b 100644 --- a/code/iaas/resource-pool/pom.xml +++ b/code/iaas/resource-pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/pom.xml b/code/iaas/service-discovery/api/pom.xml index f0abacb89c..73abfc0b23 100644 --- a/code/iaas/service-discovery/api/pom.xml +++ b/code/iaas/service-discovery/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/iaas/service-discovery/server/pom.xml b/code/iaas/service-discovery/server/pom.xml index 6ffc7208d9..77cba8fd09 100644 --- a/code/iaas/service-discovery/server/pom.xml +++ b/code/iaas/service-discovery/server/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/iaas/ssh-common/pom.xml b/code/iaas/ssh-common/pom.xml index d421b56e99..c7cfb8af35 100644 --- a/code/iaas/ssh-common/pom.xml +++ b/code/iaas/ssh-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/storage-service/pom.xml b/code/iaas/storage-service/pom.xml index 792421c3c2..7669ffcb02 100644 --- a/code/iaas/storage-service/pom.xml +++ b/code/iaas/storage-service/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/iaas/task-jooq/pom.xml b/code/iaas/task-jooq/pom.xml index 1dbeb7beb8..d7d2dec2d1 100644 --- a/code/iaas/task-jooq/pom.xml +++ b/code/iaas/task-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/activity-log/pom.xml b/code/implementation/activity-log/pom.xml index 1bd309f293..d386af6a7a 100644 --- a/code/implementation/activity-log/pom.xml +++ b/code/implementation/activity-log/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/agent-instance-impl/pom.xml b/code/implementation/agent-instance-impl/pom.xml index 14e8ec8b44..0bc4bf6d4d 100644 --- a/code/implementation/agent-instance-impl/pom.xml +++ b/code/implementation/agent-instance-impl/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml cattle-agent-instance-impl diff --git a/code/implementation/docker/api/pom.xml b/code/implementation/docker/api/pom.xml index 9bcf52374c..234788d92c 100644 --- a/code/implementation/docker/api/pom.xml +++ b/code/implementation/docker/api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/docker/common/pom.xml b/code/implementation/docker/common/pom.xml index 13b1b466d5..a63e32cfed 100644 --- a/code/implementation/docker/common/pom.xml +++ b/code/implementation/docker/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/docker/compute/pom.xml b/code/implementation/docker/compute/pom.xml index 96200c1fd7..da47de1556 100644 --- a/code/implementation/docker/compute/pom.xml +++ b/code/implementation/docker/compute/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/docker/machine/pom.xml b/code/implementation/docker/machine/pom.xml index 39370f6627..ce2d7e17a5 100644 --- a/code/implementation/docker/machine/pom.xml +++ b/code/implementation/docker/machine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/docker/storage/pom.xml b/code/implementation/docker/storage/pom.xml index a99aafa8c7..dc6142ffd3 100644 --- a/code/implementation/docker/storage/pom.xml +++ b/code/implementation/docker/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml @@ -21,12 +21,12 @@ io.cattle cattle-docker-common - 0.183.370 + 0.183.371 io.cattle cattle-iaas-allocator - 0.183.370 + 0.183.371 diff --git a/code/implementation/extension-api/pom.xml b/code/implementation/extension-api/pom.xml index 71b64504bb..6c472b625f 100644 --- a/code/implementation/extension-api/pom.xml +++ b/code/implementation/extension-api/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml cattle-extension-api diff --git a/code/implementation/hazelcast/common/pom.xml b/code/implementation/hazelcast/common/pom.xml index 91e8c3ebbd..49a7fe1e19 100644 --- a/code/implementation/hazelcast/common/pom.xml +++ b/code/implementation/hazelcast/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/eventing/pom.xml b/code/implementation/hazelcast/eventing/pom.xml index 0cf46c743e..6395b8e076 100644 --- a/code/implementation/hazelcast/eventing/pom.xml +++ b/code/implementation/hazelcast/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/lock/pom.xml b/code/implementation/hazelcast/lock/pom.xml index b3f0d155f2..0c98300d83 100644 --- a/code/implementation/hazelcast/lock/pom.xml +++ b/code/implementation/hazelcast/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/host-api/pom.xml b/code/implementation/host-api/pom.xml index f9e5e1fec4..db8cc65e01 100644 --- a/code/implementation/host-api/pom.xml +++ b/code/implementation/host-api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/host-stats/pom.xml b/code/implementation/host-stats/pom.xml index 660dfef481..d9f927ac20 100644 --- a/code/implementation/host-stats/pom.xml +++ b/code/implementation/host-stats/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/register/pom.xml b/code/implementation/register/pom.xml index 3320f2477e..5e1a7abbce 100644 --- a/code/implementation/register/pom.xml +++ b/code/implementation/register/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/sample-setup/pom.xml b/code/implementation/sample-setup/pom.xml index fe2a079792..76ccd32538 100644 --- a/code/implementation/sample-setup/pom.xml +++ b/code/implementation/sample-setup/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/settings-api/pom.xml b/code/implementation/settings-api/pom.xml index d9fc5e4ba3..15214b8e4a 100644 --- a/code/implementation/settings-api/pom.xml +++ b/code/implementation/settings-api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/simulator/agent-connection/pom.xml b/code/implementation/simulator/agent-connection/pom.xml index 8f015eb9e7..b8449d6747 100644 --- a/code/implementation/simulator/agent-connection/pom.xml +++ b/code/implementation/simulator/agent-connection/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/simulator/storage/pom.xml b/code/implementation/simulator/storage/pom.xml index 8b7541304e..c03f240f5f 100644 --- a/code/implementation/simulator/storage/pom.xml +++ b/code/implementation/simulator/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../../parent/pom.xml diff --git a/code/implementation/system-stack/pom.xml b/code/implementation/system-stack/pom.xml index 9a4ce37680..7739ca855b 100644 --- a/code/implementation/system-stack/pom.xml +++ b/code/implementation/system-stack/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/implementation/vm/pom.xml b/code/implementation/vm/pom.xml index c5a0b10947..c0f28e9678 100644 --- a/code/implementation/vm/pom.xml +++ b/code/implementation/vm/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml cattle-vm diff --git a/code/meta-parent/pom.xml b/code/meta-parent/pom.xml index bc83349a15..3f505caf07 100644 --- a/code/meta-parent/pom.xml +++ b/code/meta-parent/pom.xml @@ -9,7 +9,7 @@ 4.0.0 io.cattle cattle-meta-parent - 0.183.370 + 0.183.371 pom PastureStack Orchestration Engine Compatibility orchestration engine for the PastureStack server. diff --git a/code/packaging/app-config/pom.xml b/code/packaging/app-config/pom.xml index 75bb5eb853..4adb9128a9 100644 --- a/code/packaging/app-config/pom.xml +++ b/code/packaging/app-config/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml jar diff --git a/code/packaging/app/pom.xml b/code/packaging/app/pom.xml index 717892c4d6..e9cb140826 100644 --- a/code/packaging/app/pom.xml +++ b/code/packaging/app/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml war diff --git a/code/packaging/bundle/pom.xml b/code/packaging/bundle/pom.xml index 36cd201915..42cebfc6f2 100644 --- a/code/packaging/bundle/pom.xml +++ b/code/packaging/bundle/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/packaging/dev/pom.xml b/code/packaging/dev/pom.xml index 264874cb09..26a98e7cda 100644 --- a/code/packaging/dev/pom.xml +++ b/code/packaging/dev/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/packaging/meta/pom.xml b/code/packaging/meta/pom.xml index d816fdcef5..aac79dcd75 100644 --- a/code/packaging/meta/pom.xml +++ b/code/packaging/meta/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../../parent/pom.xml diff --git a/code/parent/pom.xml b/code/parent/pom.xml index eed20101eb..40b9995fe9 100644 --- a/code/parent/pom.xml +++ b/code/parent/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-meta-parent ../meta-parent/pom.xml - 0.183.370 + 0.183.371 pom diff --git a/docs/releases/orchestration-engine-0.183.371.md b/docs/releases/orchestration-engine-0.183.371.md new file mode 100644 index 0000000000..d0ea773eee --- /dev/null +++ b/docs/releases/orchestration-engine-0.183.371.md @@ -0,0 +1,9 @@ +# Engine 0.183.371 candidate + +Corrects typed Host name/description preview and receipt checks for native Docker Hosts. The owner validates the Host model separately from driver kind and rejects other native models. Native role/Key authority, human approval, locking, one durable claim and audit remain unchanged. + +The internal Catalog launcher can perform its normal API version-root/schema metadata handshake under the original verified delegation. Fresh event, owner, source and frozen-Stack checks remain mandatory; metadata reads are repeatable and original business writes retain their replay protections. Invalid routes and untrusted proofs remain denied, with safe audit attribution. + +Builders apply the existing pinned final security packages before the frozen base plan, avoiding obsolete intermediate-package downloads. Python/Curl/CA bootstrap pins are aligned with their existing final versions; the normal APT local-deb solver installs the complete pinned families. Final versions, source URLs, SHA pins, security lock and snapshot dates remain unchanged. + +Focused source checks passed. All 166 prior required Java methods remain, with three Host and four Catalog methods added (173 total). Official CI, original artifact/security qualification, publication and Server/runtime acceptance remain pending. No whole-family completion is claimed. diff --git a/pom.xml b/pom.xml index 9ca5d6b22e..707fb23277 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.cattle cattle-parent - 0.183.370 + 0.183.371 code/parent/pom.xml cattle diff --git a/resources/pom.xml b/resources/pom.xml index f54afa7159..d8f1aa4002 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.370 + 0.183.371 ../code/parent/pom.xml diff --git a/scripts/api-key-operation-contract.json b/scripts/api-key-operation-contract.json index 90bfa3e39d..e85e8a502a 100644 --- a/scripts/api-key-operation-contract.json +++ b/scripts/api-key-operation-contract.json @@ -10,7 +10,7 @@ "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 458, - "sha256": "7082897c9cfe9d80534ac02a871e0ef4e79356f1c23a618aadd8616be89c3069", + "sha256": "59ec478abbedf97403a9615c80785b9f69fc289b703650b8316c03730c1244f7", "anchors": [ "AbstractObjectManager.java", "AbstractSchemaFactory.java", @@ -260,114 +260,184 @@ "tupleCount": 1396, "tupleSha256": "8f1c0fb378663b0b3d272efba2f211cb210eb6f17cb8035aefa33b56feeaf36a", "extensionReview": { - "version": "unreleased-370-host-metadata-readable-workloads-and-catalog-binding", + "version": "unreleased-371-native-host-model-and-catalog-version-bootstrap", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Host metadata plans accept only name and description and retain native PUT/role/Key ceilings, exact human approval, revision and one claim. Host detail and Volume usage resolve only freshly authorized container, ordinary Service and Stack relations through bounded native queries. The SEM fallback requires a genuinely NULL persisted parent at capture and final verification; multiple, hidden, removed or moved relations remain unknown without IDs or counts. Catalog lifecycle handlers are lazily composed into the native ObjectManager so the new Stack ID is durably bound before CREATE process scheduling. Ordinary creates and output filters remain unchanged. No new tool, Host lifecycle action, generic endpoint, operation grant or secret output is added.", + "scope": "Host metadata checks validate the native Host model independently of driver kind. Event-local Catalog delegation supports only exact GET v1/v2-beta version-root metadata and normal schema reads after proof/event/owner/source/frozenStack validation; metadata is repeatable while original writes retain claims/replay boundaries. Safe rejection audit does not attribute forged proofs to a plan or Key. All native role/Key, schema, lock, human approval and business-resource permission rules remain unchanged.", "previous": { - "version": "unreleased-369-volume-usage-safe-read", + "version": "unreleased-370-host-metadata-readable-workloads-and-catalog-binding", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Volume detail adds a read-only bounded usage-reference projection through the existing typed resource view. Native SQL retrieves at most 51 relationships before materialization and exposes at most 50 authorized container targets. Current native object authorization and an exact attested Key query scope remain mandatory; unsupported scoped managers return unknown. Hidden targets create no synthetic API/audit entries. The selected Volume read retains canonical audit and direct hidden-resource requests retain DENY audit. Closed scalar fields reuse native data-field precedence and current-role schema wrapping, excluding transformed/create-only fields before access; parent names require a readable persisted Stack relationship and a fresh authorized Stack read. Completeness is always unknown; no total, hidden identity/count, host source path, write, grant or deletion-oracle change.", + "scope": "Host metadata plans accept only name and description and retain native PUT/role/Key ceilings, exact human approval, revision and one claim. Host detail and Volume usage resolve only freshly authorized container, ordinary Service and Stack relations through bounded native queries. The SEM fallback requires a genuinely NULL persisted parent at capture and final verification; multiple, hidden, removed or moved relations remain unknown without IDs or counts. Catalog lifecycle handlers are lazily composed into the native ObjectManager so the new Stack ID is durably bound before CREATE process scheduling. Ordinary creates and output filters remain unchanged. No new tool, Host lifecycle action, generic endpoint, operation grant or secret output is added.", "previous": { - "version": "unreleased-368-external-service-create-tcp-healthcheck", + "version": "unreleased-369-volume-usage-safe-read", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "External Service CREATE accepts an optional closed TCP healthCheck subset through its existing typed plan owner. Exactly the five caller fields are reviewed; port is required 1..65535 and four omitted timing fields are not filled. The positive integer timing subset matches existing typed Service validation; native metadata declares no timing minimum or defaults. HTTP controls and unknown, null, wrong-type or fractional values are rejected. The detached native body alone carries requestLine=null. Native schema/role projection is rechecked before preparation and dispatch; exact TCP readback is required for a verified receipt. No UPDATE healthCheck support, new HTTP route, operation semantics, role/Key grant, or approval bypass. Omitted healthCheck retains None and ordinary Service health checks are unchanged.", + "scope": "Volume detail adds a read-only bounded usage-reference projection through the existing typed resource view. Native SQL retrieves at most 51 relationships before materialization and exposes at most 50 authorized container targets. Current native object authorization and an exact attested Key query scope remain mandatory; unsupported scoped managers return unknown. Hidden targets create no synthetic API/audit entries. The selected Volume read retains canonical audit and direct hidden-resource requests retain DENY audit. Closed scalar fields reuse native data-field precedence and current-role schema wrapping, excluding transformed/create-only fields before access; parent names require a readable persisted Stack relationship and a fresh authorized Stack read. Completeness is always unknown; no total, hidden identity/count, host source path, write, grant or deletion-oracle change.", "previous": { - "version": "unreleased-367-service-upgrade-strategy-audit-preview-native-action-lock-order-and-rollback-fence", + "version": "unreleased-368-external-service-create-tcp-healthcheck", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Complete published366/current367 production schema-handler-process unions were independently re-extracted. All1396 HTTP tuples, operation semantics and admission dispositions remain exact. Existing Service upgrade plans gain three bounded optional strategy inputs and a closed read-only effective strategy, with repeated native port/volume preflight and unchanged current role/Key/revision/claim checks. The typed preview checks its original closed field set and original three strategy values before native coercion, retaining only a sticky rejection boolean with unchanged serviceId/image and action semantics. The typed Audit owner preserves a boolean closed-input rejection before ordinary schema sanitization and records its existing canonical ERROR. No generic schema filtering, new public operation or global mutation grant. Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults.", + "scope": "External Service CREATE accepts an optional closed TCP healthCheck subset through its existing typed plan owner. Exactly the five caller fields are reviewed; port is required 1..65535 and four omitted timing fields are not filled. The positive integer timing subset matches existing typed Service validation; native metadata declares no timing minimum or defaults. HTTP controls and unknown, null, wrong-type or fractional values are rejected. The detached native body alone carries requestLine=null. Native schema/role projection is rechecked before preparation and dispatch; exact TCP readback is required for a verified receipt. No UPDATE healthCheck support, new HTTP route, operation semantics, role/Key grant, or approval bypass. Omitted healthCheck retains None and ordinary Service health checks are unchanged.", "previous": { - "version": "unreleased-366-service-create-native-ports-healthcheck-and-catalog-projection", + "version": "unreleased-367-service-upgrade-strategy-audit-preview-native-action-lock-order-and-rollback-fence", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Complete 365/366 production schema-handler-process unions were independently re-extracted. All 1396 HTTP tuples, operation semantics and registered/unsupported dispositions remain exact. Existing typed Service create adds bounded ports/healthCheck inputs and reuses native preflight before preview/confirmation/claim; normal manager validation remains. Three current role projections add only read access to the new server-produced executionResult, with existing plan owner/current authorization and successful verified Service UUID binding retained. No existing role/action/CRUD grant is changed. Native IPv6 formatting and stricter create readback are functional changes, not new public operation grants. Environment/private human input remains unsupported. Nine direct native/helper source owners are explicitly bound. Catalog Materializer now supplies the native dynamic data.fields map to its existing current-role schema projection; real transformer/native-schema tests verify business values and hidden/create-only/unlisted field exclusions. Production-only selection and historical producer355 remain unchanged.", - "materializerDynamicProjection": { - "source": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", - "rootCause": "DockerTransformer stores imageUuid/environment/ports/logConfig in InstanceRecord data.fields; the five-argument WrappedResource constructor did not receive that dynamic value map, so the real role-schema projection omitted required business values.", - "fix": "Pass a fresh LinkedHashMap copy of DataUtils.getFields(instance) to the seven-argument WrappedResource constructor. Keep the existing current-role SchemaFactory, ID formatter, POST and createResponse=false behavior.", - "preservedBusinessValues": [ - "native docker imageUuid", - "environment exact scalar including embedded equals", - "fixed native ports", - "logConfig driver/config", - "bean-backed service name" - ], - "visibilityBoundary": "Current native role schema remains authoritative. Hidden environment, read-on-create-only ports/logConfig and unlisted dynamic fields remain absent; no role or Key authority expansion, raw bypass or new environment-input channel.", - "focusedTests": { - "newMethods": [ - "dynamicProjectionKeepsRestrictedSchemaAndNonCreateResponseRules", - "realTransformerDynamicFieldsReachNativeRoleSchemaProjection" - ], - "existingAffectedMethods": [ - "preparedGraphSeparatesNativeMutationFromAuthorizedPublicReferenceIds" - ], - "tests": 3, - "failures": 0, - "errors": 0, - "skipped": 0, - "evidenceFile": "engine366-materializer-dynamic-fields-focused-20261102.json", - "evidenceSha256": "24ea39c3caf6f6664735e67308246db34a07c59f384cccea48d9e2b171cbd35a", - "usesRealDockerTransformerAndNativeSchemas": true - }, - "runtimeAcceptance": "PENDING_MATCHING_ARTIFACT_AND_NATIVE_QA; no live pass claimed" - }, + "scope": "Complete published366/current367 production schema-handler-process unions were independently re-extracted. All1396 HTTP tuples, operation semantics and admission dispositions remain exact. Existing Service upgrade plans gain three bounded optional strategy inputs and a closed read-only effective strategy, with repeated native port/volume preflight and unchanged current role/Key/revision/claim checks. The typed preview checks its original closed field set and original three strategy values before native coercion, retaining only a sticky rejection boolean with unchanged serviceId/image and action semantics. The typed Audit owner preserves a boolean closed-input rejection before ordinary schema sanitization and records its existing canonical ERROR. No generic schema filtering, new public operation or global mutation grant. Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults.", "previous": { - "version": "unreleased-365-catalog-private-native-wire-fix", + "version": "unreleased-366-service-create-native-ports-healthcheck-and-catalog-projection", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "The fixed-JSON Catalog private-envelope consumer decodes exactly one canonical standard Base64 layer returned by the existing Go Secrets create/rewrap contract, then parses JSON and rechecks version plus exact plan/actor/project/original-Key binding. Global SecretsService.decrypt and its legacy consumers are unchanged. The decoded JSON remains limited to 512 KiB; Base64 wire has its separate 4*ceil(limit/3) bound. Malformed, nested, raw JSON, oversized or rebound input still produces the fixed safe 503. This adds no authorization relaxation. The prior substantive standalone cleanup authorization fix and Alias safe 422 validation remain unchanged; the whole 365 candidate is not zero authorization-semantic delta. Historical producer355 and complete previous reviews remain preserved.", + "scope": "Complete 365/366 production schema-handler-process unions were independently re-extracted. All 1396 HTTP tuples, operation semantics and registered/unsupported dispositions remain exact. Existing typed Service create adds bounded ports/healthCheck inputs and reuses native preflight before preview/confirmation/claim; normal manager validation remains. Three current role projections add only read access to the new server-produced executionResult, with existing plan owner/current authorization and successful verified Service UUID binding retained. No existing role/action/CRUD grant is changed. Native IPv6 formatting and stricter create readback are functional changes, not new public operation grants. Environment/private human input remains unsupported. Nine direct native/helper source owners are explicitly bound. Catalog Materializer now supplies the native dynamic data.fields map to its existing current-role schema projection; real transformer/native-schema tests verify business values and hidden/create-only/unlisted field exclusions. Production-only selection and historical producer355 remain unchanged.", + "materializerDynamicProjection": { + "source": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", + "rootCause": "DockerTransformer stores imageUuid/environment/ports/logConfig in InstanceRecord data.fields; the five-argument WrappedResource constructor did not receive that dynamic value map, so the real role-schema projection omitted required business values.", + "fix": "Pass a fresh LinkedHashMap copy of DataUtils.getFields(instance) to the seven-argument WrappedResource constructor. Keep the existing current-role SchemaFactory, ID formatter, POST and createResponse=false behavior.", + "preservedBusinessValues": [ + "native docker imageUuid", + "environment exact scalar including embedded equals", + "fixed native ports", + "logConfig driver/config", + "bean-backed service name" + ], + "visibilityBoundary": "Current native role schema remains authoritative. Hidden environment, read-on-create-only ports/logConfig and unlisted dynamic fields remain absent; no role or Key authority expansion, raw bypass or new environment-input channel.", + "focusedTests": { + "newMethods": [ + "dynamicProjectionKeepsRestrictedSchemaAndNonCreateResponseRules", + "realTransformerDynamicFieldsReachNativeRoleSchemaProjection" + ], + "existingAffectedMethods": [ + "preparedGraphSeparatesNativeMutationFromAuthorizedPublicReferenceIds" + ], + "tests": 3, + "failures": 0, + "errors": 0, + "skipped": 0, + "evidenceFile": "engine366-materializer-dynamic-fields-focused-20261102.json", + "evidenceSha256": "24ea39c3caf6f6664735e67308246db34a07c59f384cccea48d9e2b171cbd35a", + "usesRealDockerTransformerAndNativeSchemas": true + }, + "runtimeAcceptance": "PENDING_MATCHING_ARTIFACT_AND_NATIVE_QA; no live pass claimed" + }, "previous": { - "version": "unreleased-365-native-validation-cleanup-fixes", + "version": "unreleased-365-catalog-private-native-wire-fix", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "The existing HTTP tuple union and operation classifications remain exact. Derived native standalone cleanup authorization changes substantively: a CUSTOM native container DELETE may finish only its original private device-0 OS allocation map after the exact verified instance.remove -> volume.remove (optionally volume.deallocate) scheduling spine. The live spine pins complete UUID/FK/owner references; detached work rechecks that same immutable edge and current actor, role, Key revision/expiry, root grant and explicit dependency DENY. Only this pinned job may read historical removing/removed/purging/purged Instance/Volume ancestors; generic resource resolvers, unbound old jobs and public grants are unchanged. Alias native validation exceptions preserve the established safe 422 InvalidBody response instead of becoming a 500; this adds no grant. This is not zero authorization-semantic delta. Historical producer355 is unchanged.", + "scope": "The fixed-JSON Catalog private-envelope consumer decodes exactly one canonical standard Base64 layer returned by the existing Go Secrets create/rewrap contract, then parses JSON and rechecks version plus exact plan/actor/project/original-Key binding. Global SecretsService.decrypt and its legacy consumers are unchanged. The decoded JSON remains limited to 512 KiB; Base64 wire has its separate 4*ceil(limit/3) bound. Malformed, nested, raw JSON, oversized or rebound input still produces the fixed safe 503. This adds no authorization relaxation. The prior substantive standalone cleanup authorization fix and Alias safe 422 validation remain unchanged; the whole 365 candidate is not zero authorization-semantic delta. Historical producer355 and complete previous reviews remain preserved.", "previous": { - "version": "unreleased-364-native-lifecycle-fixes", + "version": "unreleased-365-native-validation-cleanup-fixes", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Exact extraction retains the existing HTTP tuples and operation classifications. Derived native private-image cleanup authorization changes substantively: a CUSTOM container DELETE may authorize only its proven private OS-image/cache dependency graph under verified instance.remove and volume.remove execution frames, with current owner, Key revision, expiry, root grant and explicit dependency deny checks retained. External target reconciliation marks an absent or different exact desired hostname as a bad unit for existing expose-map cleanup, instead of throwing on null or retaining substring matches. This is not a zero authorization-semantic-delta claim. Historical producer identity is unchanged.", + "scope": "The existing HTTP tuple union and operation classifications remain exact. Derived native standalone cleanup authorization changes substantively: a CUSTOM native container DELETE may finish only its original private device-0 OS allocation map after the exact verified instance.remove -> volume.remove (optionally volume.deallocate) scheduling spine. The live spine pins complete UUID/FK/owner references; detached work rechecks that same immutable edge and current actor, role, Key revision/expiry, root grant and explicit dependency DENY. Only this pinned job may read historical removing/removed/purging/purged Instance/Volume ancestors; generic resource resolvers, unbound old jobs and public grants are unchanged. Alias native validation exceptions preserve the established safe 422 InvalidBody response instead of becoming a 500; this adds no grant. This is not zero authorization-semantic delta. Historical producer355 is unchanged.", "previous": { - "version": "unreleased-363-external-review-before", + "version": "unreleased-364-native-lifecycle-fixes", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "External Service review before values read the existing hostname/externalIpAddresses dynamic data.fields using DataAccessor, retaining the existing safe displayed projection. No HTTP declaration, grant, operation classification, owner or CAS contract changes. Historical producer identity is unchanged.", + "scope": "Exact extraction retains the existing HTTP tuples and operation classifications. Derived native private-image cleanup authorization changes substantively: a CUSTOM container DELETE may authorize only its proven private OS-image/cache dependency graph under verified instance.remove and volume.remove execution frames, with current owner, Key revision, expiry, root grant and explicit dependency deny checks retained. External target reconciliation marks an absent or different exact desired hostname as a bad unit for existing expose-map cleanup, instead of throwing on null or retaining substring matches. This is not a zero authorization-semantic-delta claim. Historical producer identity is unchanged.", "previous": { - "version": "unreleased-362-service-index-cleanup", + "version": "unreleased-363-external-review-before", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "The native Service planner and remove owner may schedule only exact serviceindex.remove cleanup under their original update/delete operation. Current actor/role/Key/revision/expiry/revocation, precise Index/Service/Stack UUID/account/FK pins and explicit child DENY remain required. First trusted scheduling binds private references; detached workers cannot invent them. No HTTP declaration, public ServiceIndex scope grant, method or operation classification is added or changed. Historical producer identity is unchanged.", + "scope": "External Service review before values read the existing hostname/externalIpAddresses dynamic data.fields using DataAccessor, retaining the existing safe displayed projection. No HTTP declaration, grant, operation classification, owner or CAS contract changes. Historical producer identity is unchanged.", "previous": { - "version": "unreleased-361-runtime-workflows", + "version": "unreleased-362-service-index-cleanup", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "Certificate metadata uses the existing typed plan and native PUT. Durable typed decision audit admits exactly the reviewed native domains. The fixed audit CLI receives only a validated local operator upstream reconstructed from fixed HTTP(S)/loopback literals and a bounded numeric port; no credential environment or caller URL is admitted. Standalone Instance DELETE retains only its verified native remove -> retired NIC remove -> exact IP deactivate ownership graph, current actor/Key ceilings and explicit child DENY. Typed child AUTHORIZATION_DENIED remains terminal instead of UNKNOWN retries. No HTTP declaration or operation classification is added or changed; historical producer identity is unchanged.", + "scope": "The native Service planner and remove owner may schedule only exact serviceindex.remove cleanup under their original update/delete operation. Current actor/role/Key/revision/expiry/revocation, precise Index/Service/Stack UUID/account/FK pins and explicit child DENY remain required. First trusted scheduling binds private references; detached workers cannot invent them. No HTTP declaration, public ServiceIndex scope grant, method or operation classification is added or changed. Historical producer identity is unchanged.", "previous": { - "version": "unreleased-360-domains", + "version": "unreleased-361-runtime-workflows", "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", - "scope": "ExternalService and DNS Alias typed owner workflows add no raw HTTP route; exact dnsservice.setservicelinks is update, service factory/ownership checks remain native. One fixed audit-query read POST; existing safe-view source input expands to four closed native read sources. Complete baseline/current schema-handler-process unions are re-extracted. Exactly one existing dnsService.setservicelinks classification changes to update; all other existing tuples remain unchanged and producer history remains unchanged. No audit raw payload, management operation, serviceFull identity, SQL reader or arbitrary proxy is added.", + "scope": "Certificate metadata uses the existing typed plan and native PUT. Durable typed decision audit admits exactly the reviewed native domains. The fixed audit CLI receives only a validated local operator upstream reconstructed from fixed HTTP(S)/loopback literals and a bounded numeric port; no credential environment or caller URL is admitted. Standalone Instance DELETE retains only its verified native remove -> retired NIC remove -> exact IP deactivate ownership graph, current actor/Key ceilings and explicit child DENY. Typed child AUTHORIZATION_DENIED remains terminal instead of UNKNOWN retries. No HTTP declaration or operation classification is added or changed; historical producer identity is unchanged.", "previous": { - "version": "0.183.359", - "scope": "Source-only Catalog launch plus stopped-resource Archive/restore extension; Archive adds no HTTP declaration: full immutable 358 schema/handler/process union re-extracted. Historical producer unchanged. Fixed metadata/options reads and durable create-plan routes are new; only exact Service.setservicelinks changes to update. Typed launch still requires original live role and Key read/create/update at the destination, references, source CAS, Cookie-human-only completion/approval, normal managers and per-child exact-graph authorization. Neither classification nor local tests prove runtime acceptance.", + "version": "unreleased-360-domains", + "status": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", + "scope": "ExternalService and DNS Alias typed owner workflows add no raw HTTP route; exact dnsservice.setservicelinks is update, service factory/ownership checks remain native. One fixed audit-query read POST; existing safe-view source input expands to four closed native read sources. Complete baseline/current schema-handler-process unions are re-extracted. Exactly one existing dnsService.setservicelinks classification changes to update; all other existing tuples remain unchanged and producer history remains unchanged. No audit raw payload, management operation, serviceFull identity, SQL reader or arbitrary proxy is added.", "previous": { - "version": "0.183.358", - "scope": "Source-only managed lifecycle and native-name-query fixes. All 1386 HTTP declarations and semantic tuples are unchanged. Original producer identity is unchanged. Not runtime acceptance.", + "version": "0.183.359", + "scope": "Source-only Catalog launch plus stopped-resource Archive/restore extension; Archive adds no HTTP declaration: full immutable 358 schema/handler/process union re-extracted. Historical producer unchanged. Fixed metadata/options reads and durable create-plan routes are new; only exact Service.setservicelinks changes to update. Typed launch still requires original live role and Key read/create/update at the destination, references, source CAS, Cookie-human-only completion/approval, normal managers and per-child exact-graph authorization. Neither classification nor local tests prove runtime acceptance.", "previous": { - "version": "0.183.357", - "scope": "Source-only B1/B2 runtime read fixes. All 1386 HTTP declarations and semantic tuples are unchanged. Original producer identity is unchanged. Not runtime acceptance.", + "version": "0.183.358", + "scope": "Source-only managed lifecycle and native-name-query fixes. All 1386 HTTP declarations and semantic tuples are unchanged. Original producer identity is unchanged. Not runtime acceptance.", "previous": { - "version": "0.183.356", - "scope": "Source-only B1/B2 extension: re-extracted the complete baseline and current schema/handler/process union. Original producer identity is unchanged. Seven typed API tuples are added; only the two exact stack service actions change classification. Classification does not grant admission or prove asynchronous completion.", + "version": "0.183.357", + "scope": "Source-only B1/B2 runtime read fixes. All 1386 HTTP declarations and semantic tuples are unchanged. Original producer identity is unchanged. Not runtime acceptance.", "previous": { - "version": "0.183.355", - "scope": "The original 1371 operation tuples are unchanged. Eight declarations were re-extracted from the three explicit AI resource schemas and reviewed against ApiKeyOperations. Classification does not imply Key admission: plan collection GET is human-only, and confirm/cancel reject every API Key.", + "version": "0.183.356", + "scope": "Source-only B1/B2 extension: re-extracted the complete baseline and current schema/handler/process union. Original producer identity is unchanged. Seven typed API tuples are added; only the two exact stack service actions change classification. Classification does not grant admission or prove asynchronous completion.", + "previous": { + "version": "0.183.355", + "scope": "The original 1371 operation tuples are unchanged. Eight declarations were re-extracted from the three explicit AI resource schemas and reviewed against ApiKeyOperations. Classification does not imply Key admission: plan collection GET is human-only, and confirm/cancel reject every API Key.", + "schemas": [ + "resources/content/schema/base/aiServiceDescriptionUpdate.json", + "resources/content/schema/base/aiServiceUpgradePlan.json", + "resources/content/schema/base/aiToolCallEvent.json" + ], + "addedTuples": [ + [ + "aiServiceDescriptionUpdate", + "collection", + "POST", + "", + "", + "update" + ], + [ + "aiServiceUpgradePlan", + "collection", + "GET", + "", + "", + "read" + ], + [ + "aiServiceUpgradePlan", + "collection", + "POST", + "", + "", + "upgrade" + ], + [ + "aiServiceUpgradePlan", + "resource", + "GET", + "", + "", + "read" + ], + [ + "aiServiceUpgradePlan", + "resource", + "POST", + "confirm", + "", + "ai-plan:confirm" + ], + [ + "aiServiceUpgradePlan", + "resource", + "POST", + "apply", + "", + "upgrade" + ], + [ + "aiServiceUpgradePlan", + "resource", + "POST", + "cancel", + "", + "ai-plan:cancel" + ], + [ + "aiToolCallEvent", + "collection", + "POST", + "", + "", + "create" + ] + ] + }, "schemas": [ "resources/content/schema/base/aiServiceDescriptionUpdate.json", "resources/content/schema/base/aiServiceUpgradePlan.json", - "resources/content/schema/base/aiToolCallEvent.json" + "resources/content/schema/base/aiToolCallEvent.json", + "resources/content/schema/base/aiResourceView.json", + "resources/content/schema/base/aiResourceChangePlan.json" ], "addedTuples": [ [ - "aiServiceDescriptionUpdate", - "collection", - "POST", - "", - "", - "update" - ], - [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "collection", "GET", "", @@ -375,15 +445,15 @@ "read" ], [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "collection", "POST", "", "", - "upgrade" + "update" ], [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "resource", "GET", "", @@ -391,37 +461,134 @@ "read" ], [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "resource", "POST", - "confirm", + "apply", "", - "ai-plan:confirm" + "update" ], [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "resource", "POST", - "apply", + "cancel", "", - "upgrade" + "ai-change:cancel" ], [ - "aiServiceUpgradePlan", + "aiResourceChangePlan", "resource", "POST", - "cancel", + "confirm", "", - "ai-plan:cancel" + "ai-change:confirm" ], [ - "aiToolCallEvent", + "aiResourceView", "collection", "POST", "", "", - "create" + "read" ] + ], + "changedTuples": [ + { + "before": [ + "stack", + "resource", + "POST", + "activateservices", + "", + "action:activateservices" + ], + "after": [ + "stack", + "resource", + "POST", + "activateservices", + "", + "update" + ], + "rationale": "Existing Stack child-service lifecycle handler; fixed typed pair, live child authorization remains required." + }, + { + "before": [ + "stack", + "resource", + "POST", + "deactivateservices", + "", + "action:deactivateservices" + ], + "after": [ + "stack", + "resource", + "POST", + "deactivateservices", + "", + "update" + ], + "rationale": "Existing Stack child-service lifecycle handler; fixed typed pair, live child authorization remains required." + } + ], + "authorizationRationale": { + "aiResourceView": "Read semantic POST. Synthetic admission is followed by authorized domain reads and safe projection, never raw GET expansion.", + "aiResourceChangePlan": "Preview/apply are classified update admission only. Fixed domain adapter rechecks the actual create/update/delete operation and Key references. Collection GET and confirm/cancel are human-only; every Key is denied confirmation/cancellation.", + "stackServiceActions": "The two exact Stack batch actions preflight every persisted child with current owner and same-project/Stack checks. An authorized root grant is inherited only for dependency evaluation; explicit child DENY still wins before manager dispatch. The same recheck applies to human approval of bound-Key plans, and queued execution retains its independent live child checks. General Full and legacy admission paths are unchanged." + }, + "sourceInputChanges": [ + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", + "before": "8062f242159f18d5c21fc14ab65ce42c6cbb8a3bd6cb30dc0cb461bc9a282fbb", + "after": "9ceda0f5118d38fc45fc83bd4a16eb92717a20f7837bcabb804975600c59d2c5" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", + "before": "5b13ab34ee167596cc7f4b664dcc6afc6ab18f12442ed6d44a35b58ad08b40b9", + "after": "494a57d48564da85f14de564a82906444fa732f1adb201fe27d8c0ca88ea65e5" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", + "before": "391918c715b70d8fd9a607da01ac9acc85a1e3e04901c12cc21feb6ba1d98c2e", + "after": "b31333ffc5de2ec8fbee9d38daacd83b91578525d1fe1b61a73abac6355aa009" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanResourceManager.java", + "before": null, + "after": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "before": "be3f0ba2a4fa51a1c42fbf4fc7abe457b65420e8664301fa462474e015a6796f", + "after": "79a757118dd66055c6e7e4c03ded4b0dd8ef1965887c50a4c106e15836d4938f" + }, + { + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": null, + "after": "ec4b5ceb31e42dceb71c2fb0f475daf868a19a08ad9644dcf17ee8c06c769786" + }, + { + "path": "resources/content/schema/base/aiResourceView.json", + "before": null, + "after": "024fb697f3d5e1ed511544ff6922f3b78c351f01e74df1a36faeee3e7f3dba95" + }, + { + "path": "resources/content/schema/base/aiToolCallEvent.json", + "before": "58333f82f8842bf13ed0780251a21000a0f59a56a1ef1bc4354dd03aca4cd7e8", + "after": "6d4cfccd6f4901d71e7ba8ec0818de0f769f3bd8e41794dc4ca9616a3794bfbf" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "before": "c939bcf67064be956929820f561a5dde9512facf16eb18dc4f7910ab16e852c3", + "after": "96dcb3b12d6ba0a15e5532df71c68683b2fe87a8946ecb71c10109dea4f17585" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "before": "9080a03720c6c9a9bdc84d3d9fb82ed5e2d0cbbcf16d5774a2a842d8677731f7", + "after": "91c44ff87f288c967d38cfe0c6c1dd04e4ac09919cd6c6ba8407806a61168a2f" + } ] }, "schemas": [ @@ -429,161 +596,24 @@ "resources/content/schema/base/aiServiceUpgradePlan.json", "resources/content/schema/base/aiToolCallEvent.json", "resources/content/schema/base/aiResourceView.json", - "resources/content/schema/base/aiResourceChangePlan.json" - ], - "addedTuples": [ - [ - "aiResourceChangePlan", - "collection", - "GET", - "", - "", - "read" - ], - [ - "aiResourceChangePlan", - "collection", - "POST", - "", - "", - "update" - ], - [ - "aiResourceChangePlan", - "resource", - "GET", - "", - "", - "read" - ], - [ - "aiResourceChangePlan", - "resource", - "POST", - "apply", - "", - "update" - ], - [ - "aiResourceChangePlan", - "resource", - "POST", - "cancel", - "", - "ai-change:cancel" - ], - [ - "aiResourceChangePlan", - "resource", - "POST", - "confirm", - "", - "ai-change:confirm" - ], - [ - "aiResourceView", - "collection", - "POST", - "", - "", - "read" - ] - ], - "changedTuples": [ - { - "before": [ - "stack", - "resource", - "POST", - "activateservices", - "", - "action:activateservices" - ], - "after": [ - "stack", - "resource", - "POST", - "activateservices", - "", - "update" - ], - "rationale": "Existing Stack child-service lifecycle handler; fixed typed pair, live child authorization remains required." - }, - { - "before": [ - "stack", - "resource", - "POST", - "deactivateservices", - "", - "action:deactivateservices" - ], - "after": [ - "stack", - "resource", - "POST", - "deactivateservices", - "", - "update" - ], - "rationale": "Existing Stack child-service lifecycle handler; fixed typed pair, live child authorization remains required." - } + "resources/content/schema/base/aiResourceChangePlan.json", + "resources/content/schema/base/processPool.json" ], + "addedTuples": [], + "changedTuples": [], "authorizationRationale": { - "aiResourceView": "Read semantic POST. Synthetic admission is followed by authorized domain reads and safe projection, never raw GET expansion.", - "aiResourceChangePlan": "Preview/apply are classified update admission only. Fixed domain adapter rechecks the actual create/update/delete operation and Key references. Collection GET and confirm/cancel are human-only; every Key is denied confirmation/cancellation.", - "stackServiceActions": "The two exact Stack batch actions preflight every persisted child with current owner and same-project/Stack checks. An authorized root grant is inherited only for dependency evaluation; explicit child DENY still wins before manager dispatch. The same recheck applies to human approval of bound-Key plans, and queued execution retains its independent live child checks. General Full and legacy admission paths are unchanged." + "runtimeReadFixes": "ProcessPool owner supplies stable executor bean identity, distinct from native null database IDs and nonunique display names; the opted-in metadata field is read-only and adds no methods. AI projects only its opaque digest. activeSetting is not a list-capable AI type; native schemas are unchanged. Existing domain validator-decoded decimal IDs are consumed directly as internal row IDs, while public parseScopeId retains strict wire round-trip and normal owner/Key grants/denials remain authoritative." }, "sourceInputChanges": [ - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", - "before": "8062f242159f18d5c21fc14ab65ce42c6cbb8a3bd6cb30dc0cb461bc9a282fbb", - "after": "9ceda0f5118d38fc45fc83bd4a16eb92717a20f7837bcabb804975600c59d2c5" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", - "before": "5b13ab34ee167596cc7f4b664dcc6afc6ab18f12442ed6d44a35b58ad08b40b9", - "after": "494a57d48564da85f14de564a82906444fa732f1adb201fe27d8c0ca88ea65e5" - }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "391918c715b70d8fd9a607da01ac9acc85a1e3e04901c12cc21feb6ba1d98c2e", - "after": "b31333ffc5de2ec8fbee9d38daacd83b91578525d1fe1b61a73abac6355aa009" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanResourceManager.java", - "before": null, - "after": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050" + "before": "b31333ffc5de2ec8fbee9d38daacd83b91578525d1fe1b61a73abac6355aa009", + "after": "fcd8d3724c1a37b2b77cab3e60ee3e973b349462833c1bf0bcc8f099d6f3567c" }, { - "path": "resources/content/schema/admin/admin-auth.json", - "before": "be3f0ba2a4fa51a1c42fbf4fc7abe457b65420e8664301fa462474e015a6796f", - "after": "79a757118dd66055c6e7e4c03ded4b0dd8ef1965887c50a4c106e15836d4938f" - }, - { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": null, - "after": "ec4b5ceb31e42dceb71c2fb0f475daf868a19a08ad9644dcf17ee8c06c769786" - }, - { - "path": "resources/content/schema/base/aiResourceView.json", + "path": "resources/content/schema/base/processPool.json", "before": null, - "after": "024fb697f3d5e1ed511544ff6922f3b78c351f01e74df1a36faeee3e7f3dba95" - }, - { - "path": "resources/content/schema/base/aiToolCallEvent.json", - "before": "58333f82f8842bf13ed0780251a21000a0f59a56a1ef1bc4354dd03aca4cd7e8", - "after": "6d4cfccd6f4901d71e7ba8ec0818de0f769f3bd8e41794dc4ca9616a3794bfbf" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "before": "c939bcf67064be956929820f561a5dde9512facf16eb18dc4f7910ab16e852c3", - "after": "96dcb3b12d6ba0a15e5532df71c68683b2fe87a8946ecb71c10109dea4f17585" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "before": "9080a03720c6c9a9bdc84d3d9fb82ed5e2d0cbbcf16d5774a2a842d8677731f7", - "after": "91c44ff87f288c967d38cfe0c6c1dd04e4ac09919cd6c6ba8407806a61168a2f" + "after": "d055fc0e3bc30ffeb9567ca6a2c8cee7b7382200fe410c2e6154353305134b1b" } ] }, @@ -598,18 +628,14 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "runtimeReadFixes": "ProcessPool owner supplies stable executor bean identity, distinct from native null database IDs and nonunique display names; the opted-in metadata field is read-only and adds no methods. AI projects only its opaque digest. activeSetting is not a list-capable AI type; native schemas are unchanged. Existing domain validator-decoded decimal IDs are consumed directly as internal row IDs, while public parseScopeId retains strict wire round-trip and normal owner/Key grants/denials remain authoritative." + "managedLifecycle": "Closed engine-captured scheduling edges preserve exact managed retirement ancestry while live owner/Key/revision/explicit-DENY remains authoritative; ordinary ancestry is not broadened and named/file storage does not inherit destructive removal.", + "nativeNameSearch": "Closed native name conditions narrow the already-authorized owner query before its bounded page; literal query and cursor/parent restrictions are retained. Memory projections filter before taking a page." }, "sourceInputChanges": [ { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "b31333ffc5de2ec8fbee9d38daacd83b91578525d1fe1b61a73abac6355aa009", - "after": "fcd8d3724c1a37b2b77cab3e60ee3e973b349462833c1bf0bcc8f099d6f3567c" - }, - { - "path": "resources/content/schema/base/processPool.json", - "before": null, - "after": "d055fc0e3bc30ffeb9567ca6a2c8cee7b7382200fe410c2e6154353305134b1b" + "before": "fcd8d3724c1a37b2b77cab3e60ee3e973b349462833c1bf0bcc8f099d6f3567c", + "after": "645652a1352b74cf6ad4c2b1a9bbdfee1d168cef9df7c59e459d84c17d547c17" } ] }, @@ -619,19 +645,319 @@ "resources/content/schema/base/aiToolCallEvent.json", "resources/content/schema/base/aiResourceView.json", "resources/content/schema/base/aiResourceChangePlan.json", - "resources/content/schema/base/processPool.json" + "resources/content/schema/base/processPool.json", + "resources/content/schema/base/aiCatalogMetadataView.json", + "resources/content/schema/base/aiCatalogStackLaunchOptions.json", + "resources/content/schema/base/aiCatalogStackLaunchPlan.json", + "resources/content/schema/base/aiCatalogStackLaunchInputs.json" + ], + "addedTuples": [ + [ + "aiCatalogMetadataView", + "collection", + "POST", + "", + "", + "read" + ], + [ + "aiCatalogStackLaunchOptions", + "collection", + "POST", + "", + "", + "read" + ], + [ + "aiCatalogStackLaunchPlan", + "collection", + "GET", + "", + "", + "read" + ], + [ + "aiCatalogStackLaunchPlan", + "collection", + "POST", + "", + "", + "create" + ], + [ + "aiCatalogStackLaunchPlan", + "resource", + "GET", + "", + "", + "read" + ], + [ + "aiCatalogStackLaunchPlan", + "resource", + "POST", + "apply", + "", + "create" + ], + [ + "aiCatalogStackLaunchPlan", + "resource", + "POST", + "cancel", + "", + "ai-catalog-launch:cancel" + ], + [ + "aiCatalogStackLaunchPlan", + "resource", + "POST", + "completeInputs", + "", + "ai-catalog-launch:completeInputs" + ], + [ + "aiCatalogStackLaunchPlan", + "resource", + "POST", + "confirm", + "", + "ai-catalog-launch:confirm" + ] + ], + "changedTuples": [ + { + "before": [ + "service", + "resource", + "POST", + "setservicelinks", + "", + "action:setservicelinks" + ], + "after": [ + "service", + "resource", + "POST", + "setservicelinks", + "", + "update" + ], + "rationale": "Only the existing Service.setservicelinks owner is update. The typed launch preflights original live destination update and each delegated exact action; create does not imply update. Unknown plugin roots remain denied." + } ], - "addedTuples": [], - "changedTuples": [], "authorizationRationale": { - "managedLifecycle": "Closed engine-captured scheduling edges preserve exact managed retirement ancestry while live owner/Key/revision/explicit-DENY remains authoritative; ordinary ancestry is not broadened and named/file storage does not inherit destructive removal.", - "nativeNameSearch": "Closed native name conditions narrow the already-authorized owner query before its bounded page; literal query and cursor/parent restrictions are retained. Memory projections filter before taking a page." + "aiCatalogStackLaunchOptions": "Read-only fixed owner projection; no create grant or secret defaults.", + "aiCatalogStackLaunchPlan": "Dedicated durable plan. Preview/confirm/apply recheck original live destination read/create/update and references; completeInputs/confirm/cancel are verified Cookie-only, including for GENERAL FULL Keys. Apply claims once and normal Stack/Compose owners execute an exact per-event graph, with per-request actor/Key/source revalidation; private answers and proof never enter public DTO or audit payload.", + "serviceSetServiceLinks": "Exact Service action classified update; role schema, original live Key target scope and explicit DENY still apply. No plugin or create-to-update grant.", + "privateOwnerChannel": "Public Catalog/raw proxy initial and redirect targets reject canonical v1-catalog/ai namespace before body/upstream; fixed typed owner loopback calls retain their independent live authorization.", + "aiCatalogMetadataView": "Fixed metadata read POST. Fresh active owner/current-project membership plus live Key PLATFORM catalogTemplate read precede a fixed loopback Catalog projection. No raw proxy/accounts grant, project ancestry inference, forwarded credentials or new JWT. Fine-grained selectors fail closed; template/version logical identities are not cache row IDs." }, "sourceInputChanges": [ + { + "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiAuditPayload.java", + "before": null, + "after": "697bb6f57b26354f336e14f905bc2a87f8a41acfdf725500652100e90d4e4381" + }, + { + "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiCookieAuthentication.java", + "before": null, + "after": "b49fe3b141ac1689e49a583bab05caf7a449dc2b3495b0ba391534bc5812d3c4" + }, + { + "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/CatalogLaunchTransformAuthorization.java", + "before": null, + "after": "b104bb0aed1ae18dc7485aa1f7c7eddf3b6eb9176b7e7209cb2b89534600e4c6" + }, + { + "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", + "before": null, + "after": "1f834204996aabfd6e6d3d5bb520d35b80547c5e77b8edea8f25d8d6010cfd4e" + }, + { + "path": "code/framework/eventing/src/main/java/io/cattle/platform/eventing/model/EventDiagnostics.java", + "before": null, + "after": "affeccbf8445e1d61b525df683504de7c084642a52cec8e6fb9371ca935c3034" + }, + { + "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/request/handler/GenericWhitelistedProxy.java", + "before": null, + "after": "9606a7e0f76cb4c7eba033afc897d6d9e5d61045262d582ee94604169817608a" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", + "before": null, + "after": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchBinding.java", + "before": null, + "after": "9f75784fa67b7341439d3ffecf8bada16ef0e55a7db3bbb41d6eceed4db90e8d" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java", + "before": null, + "after": "e75f911701471e63f85b70df645040a4142704f8da94f3008ed2187c43b35a1d" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchGateway.java", + "before": null, + "after": "9a12293de31831d7dda5f2e787b809351c347bf95e7b95f6798bb7e18d5ae12c" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchGraph.java", + "before": null, + "after": "26618418c19bd7eef3dc7e5e4cd526072f3a614535a04b914e0559a20f12d875" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchInput.java", + "before": null, + "after": "3db05d510e3e057ae5d10340e0e313111de0a5a7e5558a477e91395f1f4280de" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", + "before": null, + "after": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchObservation.java", + "before": null, + "after": "e3ecb29c1585002c61a4c3a6e14e94868c4e9de6da94e5098eb5d2f14b93d82d" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsResourceManager.java", + "before": null, + "after": "fb2977ff6cbd4819f5ecaa75afdcd5c3d9a3c43add3b5c2d4614379349e74552" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsService.java", + "before": null, + "after": "318bb0e18d8a7292ff57a0b281d53eb5040e7c4a5fef388baea4aac4acf30d5d" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsStore.java", + "before": null, + "after": "b4dbbfffcb333ce11c559e10eee78ce2ae9dc1fa5d4a958c475f5a893b270c2b" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanResourceManager.java", + "before": null, + "after": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanService.java", + "before": null, + "after": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanStore.java", + "before": null, + "after": "8716e8f4038bad48098f16c9009446c4e77f379cba50d3049897aa2442fd7b22" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivate.java", + "before": null, + "after": "df91328e28fca5f15aceda4faaf57be47c8165dfc5cbf36894abd12935c8d3b2" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchResponseFilter.java", + "before": null, + "after": "0e35935d8e3ce19a843d3770b9a574b0c2d501612a8f6211f077834f7975bb60" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchRunner.java", + "before": null, + "after": "e6e530a39cc6ca2d270038f0084ed6c104f45d69ebfe7a307391e90918c0f6c9" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", + "before": "9ceda0f5118d38fc45fc83bd4a16eb92717a20f7837bcabb804975600c59d2c5", + "after": "d3cac29f7b6f61be0bda38f643115d08877d59e99b9312575f064290e73ede27" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", + "before": "494a57d48564da85f14de564a82906444fa732f1adb201fe27d8c0ca88ea65e5", + "after": "28847a8d6873d8e8fe1644a19813e8f8f9d1917a5c1b369dab8abcb882e6a9b6" + }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "fcd8d3724c1a37b2b77cab3e60ee3e973b349462833c1bf0bcc8f099d6f3567c", - "after": "645652a1352b74cf6ad4c2b1a9bbdfee1d168cef9df7c59e459d84c17d547c17" + "before": "645652a1352b74cf6ad4c2b1a9bbdfee1d168cef9df7c59e459d84c17d547c17", + "after": "95e9c38861b984e995770e5ddf6d7af21c96d1bff1d8848d1ad2e3a54722efd2" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/impl/ApiAuthenticator.java", + "before": "7a3e331dc9e23ae1689495b6ce4066a9decb8293def66fbffa6e0e404697db12", + "after": "d412d32cf7032b4bb0dde546a9e22193fc29ecae71b5c1efcbb052bd4590945d" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/integration/internal/rancher/TokenAuthLookup.java", + "before": null, + "after": "11c66db998415be5deab8a34df362dcb299a7b1a8df938ef1afb6f6602b71c91" + }, + { + "path": "code/iaas/logic-common/src/main/java/io/cattle/platform/process/common/handler/EventBasedProcessHandler.java", + "before": null, + "after": "d784906d3bd0e0399dc38cd38ddc5ee38c49850e7a51ea07d2a4a158dcb068de" + }, + { + "path": "code/iaas/logic-common/src/main/java/io/cattle/platform/process/common/handler/ProcessEventDecorator.java", + "before": null, + "after": "b0dc3d894c3ae0345e374cda8af588ac2e6c17c74f25501e6689a28d4ef934e5" + }, + { + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ArchiveValidationFilter.java", + "before": "63fbdcc3d0ac73186b181afc55d0e0aae4424a347a367724348802246cad823c", + "after": "908e6e0bf0d665d2392481f6d6e59d27c3cd89b4b35471fe587d6ed14e591890" + }, + { + "path": "code/implementation/docker/api/src/main/java/io/cattle/platform/docker/api/transform/TransformInspect.java", + "before": null, + "after": "c67974b0abbb3b7c7801b51ca95a649e95e0853fba9c1500da6b4b5977a0afb2" + }, + { + "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/IaasApiConfig.java", + "before": null, + "after": "02184dc6ae781547e850c80cf51ddb4f33cbc7b2f799aba3ca1e59c8eb9cf34f" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "before": "79a757118dd66055c6e7e4c03ded4b0dd8ef1965887c50a4c106e15836d4938f", + "after": "6ea49e1130a2b9218e6c060038c73c89be5b9e05b039d3a0b81475e892d21504" + }, + { + "path": "resources/content/schema/base/aiCatalogMetadataView.json", + "before": null, + "after": "5581897314b1cfe9135783fb8846db50ff6f9d62289b5ab35e60ab7ca8b36515" + }, + { + "path": "resources/content/schema/base/aiCatalogStackLaunchInputs.json", + "before": null, + "after": "05aa9a7b459a82efcf0331b3d6c8262e1b5c879643aef6384c67e3a12980bf4c" + }, + { + "path": "resources/content/schema/base/aiCatalogStackLaunchOptions.json", + "before": null, + "after": "30137b75ac50b6b04adec688ca56359fd7d49843f8342c423a906b998da5c12a" + }, + { + "path": "resources/content/schema/base/aiCatalogStackLaunchPlan.json", + "before": null, + "after": "5886387a8b25893d4afc5bb127dd8305646748e08a86b65002d8e0d8e05b328d" + }, + { + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": "ec4b5ceb31e42dceb71c2fb0f475daf868a19a08ad9644dcf17ee8c06c769786", + "after": "21b4acfd55e2aa64b725bacd520655daf7f707141e4226343c16a5b6fae47d8c" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "before": "96dcb3b12d6ba0a15e5532df71c68683b2fe87a8946ecb71c10109dea4f17585", + "after": "a924ebe3e6ea7a39374d7d162fd020807cb94900c849c82d286df121db27ee6c" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "before": "91c44ff87f288c967d38cfe0c6c1dd04e4ac09919cd6c6ba8407806a61168a2f", + "after": "0f977be3a4e6718753ba8a248e73f1014886afbee1d5387249e762b4146b7b73" } ] }, @@ -645,315 +971,229 @@ "resources/content/schema/base/aiCatalogMetadataView.json", "resources/content/schema/base/aiCatalogStackLaunchOptions.json", "resources/content/schema/base/aiCatalogStackLaunchPlan.json", - "resources/content/schema/base/aiCatalogStackLaunchInputs.json" + "resources/content/schema/base/aiCatalogStackLaunchInputs.json", + "resources/content/schema/base/aiAuditQuery.json", + "resources/content/schema/base/aiAuditSourcePageInput.json" ], "addedTuples": [ [ - "aiCatalogMetadataView", + "aiAuditQuery", "collection", "POST", "", "", "read" - ], - [ - "aiCatalogStackLaunchOptions", - "collection", - "POST", - "", - "", - "read" - ], - [ - "aiCatalogStackLaunchPlan", - "collection", - "GET", - "", - "", - "read" - ], - [ - "aiCatalogStackLaunchPlan", - "collection", - "POST", - "", - "", - "create" - ], - [ - "aiCatalogStackLaunchPlan", - "resource", - "GET", - "", - "", - "read" - ], - [ - "aiCatalogStackLaunchPlan", - "resource", - "POST", - "apply", - "", - "create" - ], - [ - "aiCatalogStackLaunchPlan", - "resource", - "POST", - "cancel", - "", - "ai-catalog-launch:cancel" - ], - [ - "aiCatalogStackLaunchPlan", - "resource", - "POST", - "completeInputs", - "", - "ai-catalog-launch:completeInputs" - ], - [ - "aiCatalogStackLaunchPlan", - "resource", - "POST", - "confirm", - "", - "ai-catalog-launch:confirm" ] ], "changedTuples": [ - { - "before": [ - "service", + [ + [ + "dnsService", "resource", "POST", "setservicelinks", "", "action:setservicelinks" ], - "after": [ - "service", + [ + "dnsService", "resource", "POST", "setservicelinks", "", "update" - ], - "rationale": "Only the existing Service.setservicelinks owner is update. The typed launch preflights original live destination update and each delegated exact action; create does not imply update. Unknown plugin roots remain denied." - } + ] + ] ], "authorizationRationale": { - "aiCatalogStackLaunchOptions": "Read-only fixed owner projection; no create grant or secret defaults.", - "aiCatalogStackLaunchPlan": "Dedicated durable plan. Preview/confirm/apply recheck original live destination read/create/update and references; completeInputs/confirm/cancel are verified Cookie-only, including for GENERAL FULL Keys. Apply claims once and normal Stack/Compose owners execute an exact per-event graph, with per-request actor/Key/source revalidation; private answers and proof never enter public DTO or audit payload.", - "serviceSetServiceLinks": "Exact Service action classified update; role schema, original live Key target scope and explicit DENY still apply. No plugin or create-to-update grant.", - "privateOwnerChannel": "Public Catalog/raw proxy initial and redirect targets reject canonical v1-catalog/ai namespace before body/upstream; fixed typed owner loopback calls retain their independent live authorization.", - "aiCatalogMetadataView": "Fixed metadata read POST. Fresh active owner/current-project membership plus live Key PLATFORM catalogTemplate read precede a fixed loopback Catalog projection. No raw proxy/accounts grant, project ancestry inference, forwarded credentials or new JWT. Fine-grained selectors fail closed; template/version logical identities are not cache row IDs." + "aiAuditQuery": "Read-semantic metadata admission only. Original native role/schema/current Key and SQL/row filters authorize each source page. Fixed installed broker read-only CLI aggregates using private original credentials; Engine observes complete owner output and persists canonical ALLOW/DENY/error through existing ApiKeyAuditSink. No client-reported result is authoritative.", + "aiAuditSourcePages": "Exactly project/account/apiKey/auditLog with bounded typed ID/time/page input; no independent input route. Original account/project selection, current membership and each native GET apply. Restricted Keys cannot mutate/export credentials; target-Key query requires original actual Key metadata read. Every Key source page pins revision; native API direct/raw routes retain their prior purpose guard and Full/legacy behavior." }, "sourceInputChanges": [ { "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiAuditPayload.java", - "before": null, - "after": "697bb6f57b26354f336e14f905bc2a87f8a41acfdf725500652100e90d4e4381" + "before": "697bb6f57b26354f336e14f905bc2a87f8a41acfdf725500652100e90d4e4381", + "after": "16122e192f490b7866d3b0af3ed4981184c938d8446ec9b7d4c892d68cf2d67d" }, { - "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiCookieAuthentication.java", + "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiKeyAuditSink.java", "before": null, - "after": "b49fe3b141ac1689e49a583bab05caf7a449dc2b3495b0ba391534bc5812d3c4" + "after": "c04eec56c774f68254c832cc8916b57ec000d5fce3bd07e5cafb65260a48fe9b" }, { - "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/CatalogLaunchTransformAuthorization.java", + "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditLogOutPutFilter.java", "before": null, - "after": "b104bb0aed1ae18dc7485aa1f7c7eddf3b6eb9176b7e7209cb2b89534600e4c6" + "after": "368c21b043bdfc149f3d5a8663cf12b6ad93f0efdec0df6b7e065d62200f4ac7" }, { "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", - "before": null, - "after": "1f834204996aabfd6e6d3d5bb520d35b80547c5e77b8edea8f25d8d6010cfd4e" + "before": "1f834204996aabfd6e6d3d5bb520d35b80547c5e77b8edea8f25d8d6010cfd4e", + "after": "a10a5d8e7ca754601ea2c5a876c318069b6639c073e616d283ae71694bc95179" }, { - "path": "code/framework/eventing/src/main/java/io/cattle/platform/eventing/model/EventDiagnostics.java", + "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/ProcessAuthorization.java", "before": null, - "after": "affeccbf8445e1d61b525df683504de7c084642a52cec8e6fb9371ca935c3034" + "after": "a717b03de1ec528f7047161679be436535d68fb416b573c3aaf5b09a2e44b520" }, { - "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/request/handler/GenericWhitelistedProxy.java", - "before": null, - "after": "9606a7e0f76cb4c7eba033afc897d6d9e5d61045262d582ee94604169817608a" + "path": "code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/ResourceManager.java", + "before": "8f5f72c17541e73cab9f59ad8d36c06593baef517628a923967bc4c18f82b999", + "after": "ef08c305584fe79e2218d360dbc2ce6f3b29b64f4123ac325c8473430bc0a1f1" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", - "before": null, - "after": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52" + "path": "code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/FilteredResourceManager.java", + "before": "a140040f0be95fd7872eb7f7e78f5824329725a109d7bc0c79d960dbc0709e77", + "after": "d2c6a28809df4912b2b22ce22e9f4e48304b91c993144867d05fa5d65f07ae76" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchBinding.java", + "path": "code/framework/schema/src/main/java/io/cattle/platform/schema/processor/NotWritablePostProcessor.java", "before": null, - "after": "9f75784fa67b7341439d3ffecf8bada16ef0e55a7db3bbb41d6eceed4db90e8d" + "after": "c42a2b1aea75d4daae5671c702e3242c9cd1e7d32e7a5280fc3a0f6415ca829a" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQuery.java", "before": null, - "after": "e75f911701471e63f85b70df645040a4142704f8da94f3008ed2187c43b35a1d" + "after": "6ef0559fdec19c2fe76742587dd5b5c88b5ec904ee3544f70dd6937fffe1e455" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchGateway.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryResourceManager.java", "before": null, - "after": "9a12293de31831d7dda5f2e787b809351c347bf95e7b95f6798bb7e18d5ae12c" + "after": "bdeaebe2fbc3f84be1c9bc19b0bcc7d534a5d43888cc3a95ec446d300458c8c2" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchGraph.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryRunner.java", "before": null, - "after": "26618418c19bd7eef3dc7e5e4cd526072f3a614535a04b914e0559a20f12d875" + "after": "608fa5f5b412dccdf154290094ff1d67204c092cfb60b850d8d7a3124f53c812" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchInput.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryService.java", "before": null, - "after": "3db05d510e3e057ae5d10340e0e313111de0a5a7e5558a477e91395f1f4280de" + "after": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", - "before": null, - "after": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchResponseFilter.java", + "before": "0e35935d8e3ce19a843d3770b9a574b0c2d501612a8f6211f077834f7975bb60", + "after": "9884cc5bc272d0a872ab7b48f5365863dfc8154b0e6c291fe418bdc14541be73" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchObservation.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiAuditSourcePage.java", "before": null, - "after": "e3ecb29c1585002c61a4c3a6e14e94868c4e9de6da94e5098eb5d2f14b93d82d" + "after": "4e47ab9f1fef9108e68a0471b633d3d99f5f5069591e7dcecfe57018f09b6b78" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsResourceManager.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiAuditSourcePageInput.java", "before": null, - "after": "fb2977ff6cbd4819f5ecaa75afdcd5c3d9a3c43add3b5c2d4614379349e74552" + "after": "771bdb99ca900a268c900c957d476f3f8d96b31ce45d56583e7c572f3abe73c5" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsService.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewResourceManager.java", "before": null, - "after": "318bb0e18d8a7292ff57a0b281d53eb5040e7c4a5fef388baea4aac4acf30d5d" + "after": "ea08a26aee7cfa32df3485dd9c4e2e7bd63ca5d2e680cdbabdfcdc5f3c63b8dd" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchOptionsStore.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewService.java", "before": null, - "after": "b4dbbfffcb333ce11c559e10eee78ce2ae9dc1fa5d4a958c475f5a893b270c2b" + "after": "54f9aeaeecbf135e34210417f5cf7ec128a90e05b1971fc351ed4bdd76bf8585" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanResourceManager.java", - "before": null, - "after": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", + "before": "d3cac29f7b6f61be0bda38f643115d08877d59e99b9312575f064290e73ede27", + "after": "85a065246a8f40e1d9a64d984f49368d488e5d9b712029b5e3e306ca37aeab47" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanService.java", - "before": null, - "after": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", + "before": "28847a8d6873d8e8fe1644a19813e8f8f9d1917a5c1b369dab8abcb882e6a9b6", + "after": "662bbef806ecf3b266699bb00809fe62b712c9610b36788cfb76e244de37940a" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanStore.java", - "before": null, - "after": "8716e8f4038bad48098f16c9009446c4e77f379cba50d3049897aa2442fd7b22" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", + "before": "65052468195ad36213f68dc585d63d1cebc13409f7a92704d53297e2efcb482e", + "after": "1bf968d28d977725fa426fe7f32b00d5bc322979c0bfe1e0501fc2c34c03100e" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivate.java", - "before": null, - "after": "df91328e28fca5f15aceda4faaf57be47c8165dfc5cbf36894abd12935c8d3b2" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", + "before": "95e9c38861b984e995770e5ddf6d7af21c96d1bff1d8848d1ad2e3a54722efd2", + "after": "3c299d683055c0301f93b4bc8d23252215d1d9c0746d0aaafa0b52447d3eb26b" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchResponseFilter.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiAliasServiceChange.java", "before": null, - "after": "0e35935d8e3ce19a843d3770b9a574b0c2d501612a8f6211f077834f7975bb60" + "after": "fa69385c6ffee09261466e995e59474b632302225088729de3242ccaf2b2118b" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchRunner.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiExternalServiceChange.java", "before": null, - "after": "e6e530a39cc6ca2d270038f0084ed6c104f45d69ebfe7a307391e90918c0f6c9" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", - "before": "9ceda0f5118d38fc45fc83bd4a16eb92717a20f7837bcabb804975600c59d2c5", - "after": "d3cac29f7b6f61be0bda38f643115d08877d59e99b9312575f064290e73ede27" + "after": "d9497a9c37842c09e833d434e5f361585f721a72eff9976c5b66902181f392e5" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", - "before": "494a57d48564da85f14de564a82906444fa732f1adb201fe27d8c0ca88ea65e5", - "after": "28847a8d6873d8e8fe1644a19813e8f8f9d1917a5c1b369dab8abcb882e6a9b6" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "645652a1352b74cf6ad4c2b1a9bbdfee1d168cef9df7c59e459d84c17d547c17", - "after": "95e9c38861b984e995770e5ddf6d7af21c96d1bff1d8848d1ad2e3a54722efd2" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/impl/ApiAuthenticator.java", - "before": "7a3e331dc9e23ae1689495b6ce4066a9decb8293def66fbffa6e0e404697db12", - "after": "d412d32cf7032b4bb0dde546a9e22193fc29ecae71b5c1efcbb052bd4590945d" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", + "before": null, + "after": "2ef7555c7864a2ce91a7c28951e895da412f68849b6cc3ec04350a75b35186f8" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/integration/internal/rancher/TokenAuthLookup.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", "before": null, - "after": "11c66db998415be5deab8a34df362dcb299a7b1a8df938ef1afb6f6602b71c91" + "after": "aafe0ea7a5f60f7e9dc926067318cb2d470ec066cc1e5986b76b2c79122570a4" }, { - "path": "code/iaas/logic-common/src/main/java/io/cattle/platform/process/common/handler/EventBasedProcessHandler.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", "before": null, - "after": "d784906d3bd0e0399dc38cd38ddc5ee38c49850e7a51ea07d2a4a158dcb068de" + "after": "d0605f3e94757996e699e4083b1cd314d6d68acf623358a92f9841bc75384fb9" }, { - "path": "code/iaas/logic-common/src/main/java/io/cattle/platform/process/common/handler/ProcessEventDecorator.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanStore.java", "before": null, - "after": "b0dc3d894c3ae0345e374cda8af588ac2e6c17c74f25501e6689a28d4ef934e5" + "after": "9862ed000b4b40a6a7643968313c250fad9b1e718a684dccbab8f35c3a29cd5f" }, { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ArchiveValidationFilter.java", - "before": "63fbdcc3d0ac73186b181afc55d0e0aae4424a347a367724348802246cad823c", - "after": "908e6e0bf0d665d2392481f6d6e59d27c3cd89b4b35471fe587d6ed14e591890" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeProcessObserver.java", + "before": null, + "after": "6f7898590bb64e55cb815c9b1a51f4ce00540a6268e8fa4914ef1b6b445f5a70" }, { - "path": "code/implementation/docker/api/src/main/java/io/cattle/platform/docker/api/transform/TransformInspect.java", + "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/dao/impl/ServiceConsumeMapDaoImpl.java", "before": null, - "after": "c67974b0abbb3b7c7801b51ca95a649e95e0853fba9c1500da6b4b5977a0afb2" + "after": "a127711511a3f1271d441235f9889d63e06a5c2d457974e4a45bd04f1e7e5747" }, { "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/IaasApiConfig.java", - "before": null, - "after": "02184dc6ae781547e850c80cf51ddb4f33cbc7b2f799aba3ca1e59c8eb9cf34f" + "before": "02184dc6ae781547e850c80cf51ddb4f33cbc7b2f799aba3ca1e59c8eb9cf34f", + "after": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130" }, { "path": "resources/content/schema/admin/admin-auth.json", - "before": "79a757118dd66055c6e7e4c03ded4b0dd8ef1965887c50a4c106e15836d4938f", - "after": "6ea49e1130a2b9218e6c060038c73c89be5b9e05b039d3a0b81475e892d21504" - }, - { - "path": "resources/content/schema/base/aiCatalogMetadataView.json", - "before": null, - "after": "5581897314b1cfe9135783fb8846db50ff6f9d62289b5ab35e60ab7ca8b36515" + "before": "6ea49e1130a2b9218e6c060038c73c89be5b9e05b039d3a0b81475e892d21504", + "after": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85" }, { - "path": "resources/content/schema/base/aiCatalogStackLaunchInputs.json", + "path": "resources/content/schema/base/aiAuditQuery.json", "before": null, - "after": "05aa9a7b459a82efcf0331b3d6c8262e1b5c879643aef6384c67e3a12980bf4c" + "after": "c12e694997a08c3e9849413f45b96d7d9cf2471289d35be54a60903d22e609eb" }, { - "path": "resources/content/schema/base/aiCatalogStackLaunchOptions.json", + "path": "resources/content/schema/base/aiAuditSourcePageInput.json", "before": null, - "after": "30137b75ac50b6b04adec688ca56359fd7d49843f8342c423a906b998da5c12a" + "after": "2aca5cba9323daae33a7171ac5f3bec2591cbdbffbdc32cf263ac8398a75e444" }, { - "path": "resources/content/schema/base/aiCatalogStackLaunchPlan.json", - "before": null, - "after": "5886387a8b25893d4afc5bb127dd8305646748e08a86b65002d8e0d8e05b328d" + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": "21b4acfd55e2aa64b725bacd520655daf7f707141e4226343c16a5b6fae47d8c", + "after": "90db2324422f25129edf155127da8e29303b74fe2467797d4459b1df451e9439" }, { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": "ec4b5ceb31e42dceb71c2fb0f475daf868a19a08ad9644dcf17ee8c06c769786", - "after": "21b4acfd55e2aa64b725bacd520655daf7f707141e4226343c16a5b6fae47d8c" + "path": "resources/content/schema/base/aiResourceView.json", + "before": "024fb697f3d5e1ed511544ff6922f3b78c351f01e74df1a36faeee3e7f3dba95", + "after": "5bd8e7f91b44165d8f57ebb5b17689c40b2c236d1d6a80af0ade78dba552cc73" }, { "path": "resources/content/schema/project/project-auth.json", - "before": "96dcb3b12d6ba0a15e5532df71c68683b2fe87a8946ecb71c10109dea4f17585", - "after": "a924ebe3e6ea7a39374d7d162fd020807cb94900c849c82d286df121db27ee6c" + "before": "a924ebe3e6ea7a39374d7d162fd020807cb94900c849c82d286df121db27ee6c", + "after": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59" }, { "path": "resources/content/schema/user/user-auth.json", - "before": "91c44ff87f288c967d38cfe0c6c1dd04e4ac09919cd6c6ba8407806a61168a2f", - "after": "0f977be3a4e6718753ba8a248e73f1014886afbee1d5387249e762b4146b7b73" + "before": "0f977be3a4e6718753ba8a248e73f1014886afbee1d5387249e762b4146b7b73", + "after": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e" } ] }, @@ -971,225 +1211,60 @@ "resources/content/schema/base/aiAuditQuery.json", "resources/content/schema/base/aiAuditSourcePageInput.json" ], - "addedTuples": [ - [ - "aiAuditQuery", - "collection", - "POST", - "", - "", - "read" - ] - ], - "changedTuples": [ - [ - [ - "dnsService", - "resource", - "POST", - "setservicelinks", - "", - "action:setservicelinks" - ], - [ - "dnsService", - "resource", - "POST", - "setservicelinks", - "", - "update" - ] - ] - ], + "addedTuples": [], + "changedTuples": [], "authorizationRationale": { - "aiAuditQuery": "Read-semantic metadata admission only. Original native role/schema/current Key and SQL/row filters authorize each source page. Fixed installed broker read-only CLI aggregates using private original credentials; Engine observes complete owner output and persists canonical ALLOW/DENY/error through existing ApiKeyAuditSink. No client-reported result is authoritative.", - "aiAuditSourcePages": "Exactly project/account/apiKey/auditLog with bounded typed ID/time/page input; no independent input route. Original account/project selection, current membership and each native GET apply. Restricted Keys cannot mutate/export credentials; target-Key query requires original actual Key metadata read. Every Key source page pins revision; native API direct/raw routes retain their prior purpose guard and Full/legacy behavior." + "certificate": "Only name/description; current native method/field schema, row lock, safe UUID/public fingerprint CAS, Cookie-human approval and original Key revision remain required. No PEM/key/chain field or raw proxy is admitted.", + "nativeNetworkRemoval": "Actual immutable Java execution frames include native processName. Only Instance DELETE/delete, exact persisted owner/UUID/FK and all IP maps qualify for native nic.remove reentry. Missing/stop frames, foreign/shared/reassigned children and explicit NIC/IP/map/service DENY remain denied. Live paths and Full/legacy retain their original owner behavior.", + "terminalDenial": "Only the structured synchronous child AUTHORIZATION_DENIED terminates its parent. Other exit/retry semantics and durable completion-audit replay are unchanged.", + "auditOwnerTopology": "The fixed private CLI shares the configured broker upstream. Java reconstructs only HTTP(S) loopback origins and bounded ports from its exact nonsecret operator setting after clearing all other environment; credentials stay private stdin. No HTTP or tool-specified URL is accepted.", + "audit": "Existing durable metadata-only decision owner includes the explicitly reviewed certificate/externalService/dnsService domains. It does not authorize their operation or log private material." }, "sourceInputChanges": [ - { - "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiAuditPayload.java", - "before": "697bb6f57b26354f336e14f905bc2a87f8a41acfdf725500652100e90d4e4381", - "after": "16122e192f490b7866d3b0af3ed4981184c938d8446ec9b7d4c892d68cf2d67d" - }, - { - "path": "code/framework/api/src/main/java/io/cattle/platform/api/auth/ApiKeyAuditSink.java", - "before": null, - "after": "c04eec56c774f68254c832cc8916b57ec000d5fce3bd07e5cafb65260a48fe9b" - }, - { - "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditLogOutPutFilter.java", - "before": null, - "after": "368c21b043bdfc149f3d5a8663cf12b6ad93f0efdec0df6b7e065d62200f4ac7" - }, { "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", - "before": "1f834204996aabfd6e6d3d5bb520d35b80547c5e77b8edea8f25d8d6010cfd4e", - "after": "a10a5d8e7ca754601ea2c5a876c318069b6639c073e616d283ae71694bc95179" - }, - { - "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/ProcessAuthorization.java", - "before": null, - "after": "a717b03de1ec528f7047161679be436535d68fb416b573c3aaf5b09a2e44b520" - }, - { - "path": "code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/ResourceManager.java", - "before": "8f5f72c17541e73cab9f59ad8d36c06593baef517628a923967bc4c18f82b999", - "after": "ef08c305584fe79e2218d360dbc2ce6f3b29b64f4123ac325c8473430bc0a1f1" - }, - { - "path": "code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/FilteredResourceManager.java", - "before": "a140040f0be95fd7872eb7f7e78f5824329725a109d7bc0c79d960dbc0709e77", - "after": "d2c6a28809df4912b2b22ce22e9f4e48304b91c993144867d05fa5d65f07ae76" - }, - { - "path": "code/framework/schema/src/main/java/io/cattle/platform/schema/processor/NotWritablePostProcessor.java", - "before": null, - "after": "c42a2b1aea75d4daae5671c702e3242c9cd1e7d32e7a5280fc3a0f6415ca829a" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQuery.java", - "before": null, - "after": "6ef0559fdec19c2fe76742587dd5b5c88b5ec904ee3544f70dd6937fffe1e455" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryResourceManager.java", - "before": null, - "after": "bdeaebe2fbc3f84be1c9bc19b0bcc7d534a5d43888cc3a95ec446d300458c8c2" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryRunner.java", - "before": null, - "after": "608fa5f5b412dccdf154290094ff1d67204c092cfb60b850d8d7a3124f53c812" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryService.java", - "before": null, - "after": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchResponseFilter.java", - "before": "0e35935d8e3ce19a843d3770b9a574b0c2d501612a8f6211f077834f7975bb60", - "after": "9884cc5bc272d0a872ab7b48f5365863dfc8154b0e6c291fe418bdc14541be73" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiAuditSourcePage.java", - "before": null, - "after": "4e47ab9f1fef9108e68a0471b633d3d99f5f5069591e7dcecfe57018f09b6b78" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiAuditSourcePageInput.java", - "before": null, - "after": "771bdb99ca900a268c900c957d476f3f8d96b31ce45d56583e7c572f3abe73c5" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewResourceManager.java", - "before": null, - "after": "ea08a26aee7cfa32df3485dd9c4e2e7bd63ca5d2e680cdbabdfcdc5f3c63b8dd" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewService.java", - "before": null, - "after": "54f9aeaeecbf135e34210417f5cf7ec128a90e05b1971fc351ed4bdd76bf8585" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyAuthorizationService.java", - "before": "d3cac29f7b6f61be0bda38f643115d08877d59e99b9312575f064290e73ede27", - "after": "85a065246a8f40e1d9a64d984f49368d488e5d9b712029b5e3e306ca37aeab47" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyOperations.java", - "before": "28847a8d6873d8e8fe1644a19813e8f8f9d1917a5c1b369dab8abcb882e6a9b6", - "after": "662bbef806ecf3b266699bb00809fe62b712c9610b36788cfb76e244de37940a" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", - "before": "65052468195ad36213f68dc585d63d1cebc13409f7a92704d53297e2efcb482e", - "after": "1bf968d28d977725fa426fe7f32b00d5bc322979c0bfe1e0501fc2c34c03100e" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "95e9c38861b984e995770e5ddf6d7af21c96d1bff1d8848d1ad2e3a54722efd2", - "after": "3c299d683055c0301f93b4bc8d23252215d1d9c0746d0aaafa0b52447d3eb26b" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiAliasServiceChange.java", - "before": null, - "after": "fa69385c6ffee09261466e995e59474b632302225088729de3242ccaf2b2118b" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiExternalServiceChange.java", - "before": null, - "after": "d9497a9c37842c09e833d434e5f361585f721a72eff9976c5b66902181f392e5" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", - "before": null, - "after": "2ef7555c7864a2ce91a7c28951e895da412f68849b6cc3ec04350a75b35186f8" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": null, - "after": "aafe0ea7a5f60f7e9dc926067318cb2d470ec066cc1e5986b76b2c79122570a4" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", - "before": null, - "after": "d0605f3e94757996e699e4083b1cd314d6d68acf623358a92f9841bc75384fb9" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanStore.java", - "before": null, - "after": "9862ed000b4b40a6a7643968313c250fad9b1e718a684dccbab8f35c3a29cd5f" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeProcessObserver.java", - "before": null, - "after": "6f7898590bb64e55cb815c9b1a51f4ce00540a6268e8fa4914ef1b6b445f5a70" + "before": "a10a5d8e7ca754601ea2c5a876c318069b6639c073e616d283ae71694bc95179", + "after": "cb6bb0a8ed3dda48bedd3ae89ed78a490f644c1040c5aa16463e1e701250988e" }, { - "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/dao/impl/ServiceConsumeMapDaoImpl.java", + "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/context/EngineContext.java", "before": null, - "after": "a127711511a3f1271d441235f9889d63e06a5c2d457974e4a45bd04f1e7e5747" + "after": "ba91b4da42d1f2ea5a2b9709818c2a8f4b967c2c730d5681b53d9dbe94ce8968" }, { - "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/IaasApiConfig.java", - "before": "02184dc6ae781547e850c80cf51ddb4f33cbc7b2f799aba3ca1e59c8eb9cf34f", - "after": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "before": "6ea49e1130a2b9218e6c060038c73c89be5b9e05b039d3a0b81475e892d21504", - "after": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85" + "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/impl/DefaultProcessInstanceImpl.java", + "before": null, + "after": "986e14156ce42ac72033fad21289db4e3732469378cb6c9b0ae1d0a4c36376b0" }, { - "path": "resources/content/schema/base/aiAuditQuery.json", - "before": null, - "after": "c12e694997a08c3e9849413f45b96d7d9cf2471289d35be54a60903d22e609eb" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryRunner.java", + "before": "608fa5f5b412dccdf154290094ff1d67204c092cfb60b850d8d7a3124f53c812", + "after": "684fa84a259118a13d5a1ef20f76e05592ef867492e3fc54c1dcb94c06bb5758" }, { - "path": "resources/content/schema/base/aiAuditSourcePageInput.json", - "before": null, - "after": "2aca5cba9323daae33a7171ac5f3bec2591cbdbffbdc32cf263ac8398a75e444" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", + "before": "1bf968d28d977725fa426fe7f32b00d5bc322979c0bfe1e0501fc2c34c03100e", + "after": "39ac76abfff5b7a8d95f722f72a4869443ec9c4e99cac754b2690f7ab5be382d" }, { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": "21b4acfd55e2aa64b725bacd520655daf7f707141e4226343c16a5b6fae47d8c", - "after": "90db2324422f25129edf155127da8e29303b74fe2467797d4459b1df451e9439" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", + "before": "3c299d683055c0301f93b4bc8d23252215d1d9c0746d0aaafa0b52447d3eb26b", + "after": "e755cdf32b48df5392cc6700e0d86890393828da25f957cdfc60c9e6ce157968" }, { - "path": "resources/content/schema/base/aiResourceView.json", - "before": "024fb697f3d5e1ed511544ff6922f3b78c351f01e74df1a36faeee3e7f3dba95", - "after": "5bd8e7f91b44165d8f57ebb5b17689c40b2c236d1d6a80af0ade78dba552cc73" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", + "before": "2ef7555c7864a2ce91a7c28951e895da412f68849b6cc3ec04350a75b35186f8", + "after": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417" }, { - "path": "resources/content/schema/project/project-auth.json", - "before": "a924ebe3e6ea7a39374d7d162fd020807cb94900c849c82d286df121db27ee6c", - "after": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "aafe0ea7a5f60f7e9dc926067318cb2d470ec066cc1e5986b76b2c79122570a4", + "after": "2fc7645b2ce573ae02ebc2ec7e20548dda2e0a738d8e65d056650a6c85883c3e" }, { - "path": "resources/content/schema/user/user-auth.json", - "before": "0f977be3a4e6718753ba8a248e73f1014886afbee1d5387249e762b4146b7b73", - "after": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e" + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": "90db2324422f25129edf155127da8e29303b74fe2467797d4459b1df451e9439", + "after": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e" } ] }, @@ -1210,57 +1285,25 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "certificate": "Only name/description; current native method/field schema, row lock, safe UUID/public fingerprint CAS, Cookie-human approval and original Key revision remain required. No PEM/key/chain field or raw proxy is admitted.", - "nativeNetworkRemoval": "Actual immutable Java execution frames include native processName. Only Instance DELETE/delete, exact persisted owner/UUID/FK and all IP maps qualify for native nic.remove reentry. Missing/stop frames, foreign/shared/reassigned children and explicit NIC/IP/map/service DENY remain denied. Live paths and Full/legacy retain their original owner behavior.", - "terminalDenial": "Only the structured synchronous child AUTHORIZATION_DENIED terminates its parent. Other exit/retry semantics and durable completion-audit replay are unchanged.", - "auditOwnerTopology": "The fixed private CLI shares the configured broker upstream. Java reconstructs only HTTP(S) loopback origins and bounded ports from its exact nonsecret operator setting after clearing all other environment; credentials stay private stdin. No HTTP or tool-specified URL is accepted.", - "audit": "Existing durable metadata-only decision owner includes the explicitly reviewed certificate/externalService/dnsService domains. It does not authorize their operation or log private material." + "serviceIndexRemoval": "Only actual verified service.activate/service.update/service.remove native owners can capture the serviceindex.remove edge. Fresh persisted Index/Service/Stack identities and exact ownership relations must match the original Key-bound root; queued work matches all private identity pins. Missing/wrong parent, foreign/rebound UUID/FK/account, changed Key revision or revoked/expired authority and explicit Index/Service/Stack DENY remain rejected. Full/legacy/no-Key owner behavior is unchanged.", + "operation": "The child retains the original root update/delete operation, rather than obtaining a new independent delete or project-wide resource grant. ServiceIndex is not added to the generic resolver or PROJECT_ONLY.", + "owner": "Planner and Service/Stack remove use the existing native owner. ServiceSuffixRemove keeps its normal pool-only IP allocation release; no new NIC/IP/network or HTTP proxy permission is admitted." }, "sourceInputChanges": [ { - "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", - "before": "a10a5d8e7ca754601ea2c5a876c318069b6639c073e616d283ae71694bc95179", - "after": "cb6bb0a8ed3dda48bedd3ae89ed78a490f644c1040c5aa16463e1e701250988e" - }, - { - "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/context/EngineContext.java", - "before": null, - "after": "ba91b4da42d1f2ea5a2b9709818c2a8f4b967c2c730d5681b53d9dbe94ce8968" - }, - { - "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/impl/DefaultProcessInstanceImpl.java", - "before": null, - "after": "986e14156ce42ac72033fad21289db4e3732469378cb6c9b0ae1d0a4c36376b0" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryRunner.java", - "before": "608fa5f5b412dccdf154290094ff1d67204c092cfb60b850d8d7a3124f53c812", - "after": "684fa84a259118a13d5a1ef20f76e05592ef867492e3fc54c1dcb94c06bb5758" + "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/ProcessAuthorization.java", + "before": "a717b03de1ec528f7047161679be436535d68fb416b573c3aaf5b09a2e44b520", + "after": "ae96df57002f8102f1bf5eb3c813574a3563ec9ef52133f3499299037fb29ed3" }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", - "before": "1bf968d28d977725fa426fe7f32b00d5bc322979c0bfe1e0501fc2c34c03100e", - "after": "39ac76abfff5b7a8d95f722f72a4869443ec9c4e99cac754b2690f7ab5be382d" + "before": "39ac76abfff5b7a8d95f722f72a4869443ec9c4e99cac754b2690f7ab5be382d", + "after": "b2bf0a5939e030de833957f634622fccea75bb8f00f86e454b83f910f695682d" }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "3c299d683055c0301f93b4bc8d23252215d1d9c0746d0aaafa0b52447d3eb26b", - "after": "e755cdf32b48df5392cc6700e0d86890393828da25f957cdfc60c9e6ce157968" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", - "before": "2ef7555c7864a2ce91a7c28951e895da412f68849b6cc3ec04350a75b35186f8", - "after": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": "aafe0ea7a5f60f7e9dc926067318cb2d470ec066cc1e5986b76b2c79122570a4", - "after": "2fc7645b2ce573ae02ebc2ec7e20548dda2e0a738d8e65d056650a6c85883c3e" - }, - { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": "90db2324422f25129edf155127da8e29303b74fe2467797d4459b1df451e9439", - "after": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e" + "before": "e755cdf32b48df5392cc6700e0d86890393828da25f957cdfc60c9e6ce157968", + "after": "51b21c88e5ec55dbf2a8708b4890127b9ab9668b3b1659d09a71942f11d25403" } ] }, @@ -1281,25 +1324,15 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "serviceIndexRemoval": "Only actual verified service.activate/service.update/service.remove native owners can capture the serviceindex.remove edge. Fresh persisted Index/Service/Stack identities and exact ownership relations must match the original Key-bound root; queued work matches all private identity pins. Missing/wrong parent, foreign/rebound UUID/FK/account, changed Key revision or revoked/expired authority and explicit Index/Service/Stack DENY remain rejected. Full/legacy/no-Key owner behavior is unchanged.", - "operation": "The child retains the original root update/delete operation, rather than obtaining a new independent delete or project-wide resource grant. ServiceIndex is not added to the generic resolver or PROJECT_ONLY.", - "owner": "Planner and Service/Stack remove use the existing native owner. ServiceSuffixRemove keeps its normal pool-only IP allocation release; no new NIC/IP/network or HTTP proxy permission is admitted." + "externalReviewBefore": "Only the two existing before-value reads change from absent Java getters to the existing dynamic field accessor. The existing hostname/IP syntax filtering, safe scalar projection, and explicit clearing of the other target mode remain unchanged.", + "operation": "No method, action, public resource, scope grant or operation classification is added or changed.", + "owner": "The native External Service owner, authorization and full data snapshot/CAS remain unchanged." }, "sourceInputChanges": [ { - "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/process/ProcessAuthorization.java", - "before": "a717b03de1ec528f7047161679be436535d68fb416b573c3aaf5b09a2e44b520", - "after": "ae96df57002f8102f1bf5eb3c813574a3563ec9ef52133f3499299037fb29ed3" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", - "before": "39ac76abfff5b7a8d95f722f72a4869443ec9c4e99cac754b2690f7ab5be382d", - "after": "b2bf0a5939e030de833957f634622fccea75bb8f00f86e454b83f910f695682d" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "e755cdf32b48df5392cc6700e0d86890393828da25f957cdfc60c9e6ce157968", - "after": "51b21c88e5ec55dbf2a8708b4890127b9ab9668b3b1659d09a71942f11d25403" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "2fc7645b2ce573ae02ebc2ec7e20548dda2e0a738d8e65d056650a6c85883c3e", + "after": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8" } ] }, @@ -1320,15 +1353,25 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "externalReviewBefore": "Only the two existing before-value reads change from absent Java getters to the existing dynamic field accessor. The existing hostname/IP syntax filtering, safe scalar projection, and explicit clearing of the other target mode remain unchanged.", - "operation": "No method, action, public resource, scope grant or operation classification is added or changed.", - "owner": "The native External Service owner, authorization and full data snapshot/CAS remain unchanged." + "derivedCleanup": "The new path is limited to standalone native container DELETE, its unique current device-0 OS volume, and a NULL-account private image/cache whose complete live reference graph is proven. It does not create a public Image grant or choose FULL policy; explicit root/dependency denies and fresh owner/Key checks still apply.", + "externalTargets": "The collector intentionally includes nonremoved old target maps. Missing or different exact desired hostname is stale/error so the existing bad-unit cleanup removes that map; missing maps remain not started and stop/remove semantics are retained.", + "operation": "The actual re-extracted HTTP tuple set, semantic classification map and frozen role schemas remain identical. Derived authorization and lifecycle behavior changes are reviewed separately and are not described as zero semantic delta." }, "sourceInputChanges": [ { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": "2fc7645b2ce573ae02ebc2ec7e20548dda2e0a738d8e65d056650a6c85883c3e", - "after": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", + "before": "b2bf0a5939e030de833957f634622fccea75bb8f00f86e454b83f910f695682d", + "after": "e7301721f6b7eb54084b46b31eeec364aeecd3c1c95cc646eef0b9a3ea20b82c" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", + "before": "51b21c88e5ec55dbf2a8708b4890127b9ab9668b3b1659d09a71942f11d25403", + "after": "0f836a821b080d7395a1c19f3740a7259112eb8e0126a2a356c233b699091be9" + }, + { + "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/deployment/impl/unit/ExternalDeploymentUnitInstance.java", + "before": null, + "after": "66182deff7e2e7aefa36beae2558690c81a130e2ad43025fd899cfa96f06955f" } ] }, @@ -1349,25 +1392,31 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "derivedCleanup": "The new path is limited to standalone native container DELETE, its unique current device-0 OS volume, and a NULL-account private image/cache whose complete live reference graph is proven. It does not create a public Image grant or choose FULL policy; explicit root/dependency denies and fresh owner/Key checks still apply.", - "externalTargets": "The collector intentionally includes nonremoved old target maps. Missing or different exact desired hostname is stale/error so the existing bad-unit cleanup removes that map; missing maps remain not started and stop/remove semantics are retained.", - "operation": "The actual re-extracted HTTP tuple set, semantic classification map and frozen role schemas remain identical. Derived authorization and lifecycle behavior changes are reviewed separately and are not described as zero semantic delta." + "derivedCleanup": "Only the standalone private OS map job is extended. Direct and nested true parent frames pin instance/volume/map/pool UUIDs and account/stack/FKs before detachment. Historical ancestors are read only after complete original pins and a trusted persisted child agree. Current owner/role/schema, fresh credential/revision/expiry and every root/dependency explicit DENY remain enforced. Missing refs, rebound graph, shared/named/managed storage and old canceled unbound jobs gain no authority. Existing live ancillary paths keep their prior generic authorization.", + "aliasValidation": "Existing native request validation errors retain 422 InvalidBody at the typed preview boundary, before plan allocation/apply. It does not grant access to foreign resources or add a route.", + "operation": "The actual re-extracted production HTTP tuple union, classifications and frozen role schemas remain identical. The authority c03-source-inputs-v1 selector is unchanged except the explicit EngineContext.java anchor.", + "regressionEvidence": "Regression test sources are separate proof, excluded from production authority HTTP input count and tuple extraction. Exact path/SHA: code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/apikey/StandaloneVolumePoolCleanupTest.java=e0017c0d1aa2d453cac4ca8a214e6d37e9f414ef7c1d19e599763a46755c305a; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomainTest.java=920ff179d4a0345708c32b516f974a6b8620bd48516cab98062be9d12402402f; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanServiceTest.java=d09ba2736e5f77a68dbd6dc3f365567cddf22fefef5372deabd934c19341adcd. Required original CI executed methods and qualified wholeTree are the official execution/source evidence, not this metadata review." }, "sourceInputChanges": [ + { + "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/context/EngineContext.java", + "before": "ba91b4da42d1f2ea5a2b9709818c2a8f4b967c2c730d5681b53d9dbe94ce8968", + "after": "17df7120c6b4a5e78ca643d0ea26f0fa30023bc277f2eb3b155680a97bef6b3b" + }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", - "before": "b2bf0a5939e030de833957f634622fccea75bb8f00f86e454b83f910f695682d", - "after": "e7301721f6b7eb54084b46b31eeec364aeecd3c1c95cc646eef0b9a3ea20b82c" + "before": "e7301721f6b7eb54084b46b31eeec364aeecd3c1c95cc646eef0b9a3ea20b82c", + "after": "0a102044c31c53be38095526812e8c36aa463ada9e32f5e58042a20e51e3216f" }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "51b21c88e5ec55dbf2a8708b4890127b9ab9668b3b1659d09a71942f11d25403", - "after": "0f836a821b080d7395a1c19f3740a7259112eb8e0126a2a356c233b699091be9" + "before": "0f836a821b080d7395a1c19f3740a7259112eb8e0126a2a356c233b699091be9", + "after": "76742db7c623e670415d14790daa3913f51ae1182737caa459c10d83d3a4abd4" }, { - "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/deployment/impl/unit/ExternalDeploymentUnitInstance.java", - "before": null, - "after": "66182deff7e2e7aefa36beae2558690c81a130e2ad43025fd899cfa96f06955f" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", + "before": "d0605f3e94757996e699e4083b1cd314d6d68acf623358a92f9841bc75384fb9", + "after": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d" } ] }, @@ -1388,31 +1437,15 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "derivedCleanup": "Only the standalone private OS map job is extended. Direct and nested true parent frames pin instance/volume/map/pool UUIDs and account/stack/FKs before detachment. Historical ancestors are read only after complete original pins and a trusted persisted child agree. Current owner/role/schema, fresh credential/revision/expiry and every root/dependency explicit DENY remain enforced. Missing refs, rebound graph, shared/named/managed storage and old canceled unbound jobs gain no authority. Existing live ancillary paths keep their prior generic authorization.", - "aliasValidation": "Existing native request validation errors retain 422 InvalidBody at the typed preview boundary, before plan allocation/apply. It does not grant access to foreign resources or add a route.", - "operation": "The actual re-extracted production HTTP tuple union, classifications and frozen role schemas remain identical. The authority c03-source-inputs-v1 selector is unchanged except the explicit EngineContext.java anchor.", - "regressionEvidence": "Regression test sources are separate proof, excluded from production authority HTTP input count and tuple extraction. Exact path/SHA: code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/apikey/StandaloneVolumePoolCleanupTest.java=e0017c0d1aa2d453cac4ca8a214e6d37e9f414ef7c1d19e599763a46755c305a; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomainTest.java=920ff179d4a0345708c32b516f974a6b8620bd48516cab98062be9d12402402f; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanServiceTest.java=d09ba2736e5f77a68dbd6dc3f365567cddf22fefef5372deabd934c19341adcd. Required original CI executed methods and qualified wholeTree are the official execution/source evidence, not this metadata review." + "privateWire": "Consumer compatibility correction only; existing current actor, owner, role, Key revision/expiry, approval, dependency DENY and dispatch gates are unchanged. No raw JSON or nested Base64 fallback.", + "operation": "Existing production-only c03-source-inputs-v1 selector, explicit anchors, HTTP tuples, classifications and role schemas remain exact.", + "regressionEvidence": "Separate test proof, excluded from production authority HTTP count: code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java=78f4a0cc3d366ed83987f113b8212e1019943d12539947dda6c6760f07cb6c9b; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivateTest.java=1f855b3b8d832cce524aa7743ce24db98265e051dcb9e97ec056baf0157e804e; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivateWireTest.java=257599b8217e60d99c1cd1ce182540b5361ac64539b02685425ecfd55e94154b; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchWorkflowTest.java=d73681c1e2c8b42e19e4228635005543bf1a2d708d1603eda6f07407620e0d5d. Official execution proof remains required original CI plus qualified wholeTree; local synthetic loopback tests are not runtime acceptance." }, "sourceInputChanges": [ { - "path": "code/framework/engine/src/main/java/io/cattle/platform/engine/context/EngineContext.java", - "before": "ba91b4da42d1f2ea5a2b9709818c2a8f4b967c2c730d5681b53d9dbe94ce8968", - "after": "17df7120c6b4a5e78ca643d0ea26f0fa30023bc277f2eb3b155680a97bef6b3b" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyProcessAuthorization.java", - "before": "e7301721f6b7eb54084b46b31eeec364aeecd3c1c95cc646eef0b9a3ea20b82c", - "after": "0a102044c31c53be38095526812e8c36aa463ada9e32f5e58042a20e51e3216f" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/apikey/ApiKeyTargetResolver.java", - "before": "0f836a821b080d7395a1c19f3740a7259112eb8e0126a2a356c233b699091be9", - "after": "76742db7c623e670415d14790daa3913f51ae1182737caa459c10d83d3a4abd4" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", - "before": "d0605f3e94757996e699e4083b1cd314d6d68acf623358a92f9841bc75384fb9", - "after": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivate.java", + "before": "df91328e28fca5f15aceda4faaf57be47c8165dfc5cbf36894abd12935c8d3b2", + "after": "a1f341587843e47beacf4c8d220ea3724f30c779a057555e7eccb33a2dfd2c71" } ] }, @@ -1433,17 +1466,152 @@ "addedTuples": [], "changedTuples": [], "authorizationRationale": { - "privateWire": "Consumer compatibility correction only; existing current actor, owner, role, Key revision/expiry, approval, dependency DENY and dispatch gates are unchanged. No raw JSON or nested Base64 fallback.", - "operation": "Existing production-only c03-source-inputs-v1 selector, explicit anchors, HTTP tuples, classifications and role schemas remain exact.", - "regressionEvidence": "Separate test proof, excluded from production authority HTTP count: code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegationTest.java=78f4a0cc3d366ed83987f113b8212e1019943d12539947dda6c6760f07cb6c9b; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivateTest.java=1f855b3b8d832cce524aa7743ce24db98265e051dcb9e97ec056baf0157e804e; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivateWireTest.java=257599b8217e60d99c1cd1ce182540b5361ac64539b02685425ecfd55e94154b; code/iaas/auth-logic/src/test/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchWorkflowTest.java=d73681c1e2c8b42e19e4228635005543bf1a2d708d1603eda6f07407620e0d5d. Official execution proof remains required original CI plus qualified wholeTree; local synthetic loopback tests are not runtime acceptance." + "typedCreate": "Only ports and the closed healthCheck subset enter the existing Service create adapter. Current actor/project/role/key max/revision/expiry/CAS, human approval, audit and one durable claim remain; environment and raw launchConfig remain rejected.", + "executionResult": "admin/project/user gain r only on a new read-only result field, never create/update. Existing receipt access still reauthorizes the actual same-account Service and pins ID/UUID. assignedPorts is emitted only after exact readback succeeds; approved changes remain immutable.", + "nativeOwners": "ServiceValidationFilter, PortSpec, PortPreflightInputs/Service, PortBindingAddress, ServiceCreate and ServiceDiscoveryServiceImpl are now source-bound alongside AiServiceCreateChange and ServiceCreateValidation. The same parser, preflight and persisted allocation owner remain authoritative.", + "operation": "1396 exact tuples and every derived semantic/registered/unsupported/human-only disposition are unchanged. Frozen v1 schemas and all existing role permissions are unchanged except the three explicit new r projection entries.", + "catalogProjection": "A fresh copy of InstanceRecord data.fields feeds the existing native role-schema projection with the same formatter/POST and non-create-response rules; it preserves already-authorized business values without introducing a new permission or transport.", + "acceptance": "Source extraction is not artifact, role-matrix, HTTP or asynchronous runtime acceptance; the original PR149/CI failure remains preserved." }, "sourceInputChanges": [ { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPrivate.java", - "before": "df91328e28fca5f15aceda4faaf57be47c8165dfc5cbf36894abd12935c8d3b2", - "after": "a1f341587843e47beacf4c8d220ea3724f30c779a057555e7eccb33a2dfd2c71" + "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/port/PortPreflightInputs.java", + "before": null, + "after": "237060330f0ae0c092b11018159b6bf9fa214e1e2564d57f6b139dee34c38e13", + "baselineSourceSha256": "237060330f0ae0c092b11018159b6bf9fa214e1e2564d57f6b139dee34c38e13", + "newlyBound": true, + "sourceBytesChanged": false + }, + { + "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/port/PortPreflightService.java", + "before": null, + "after": "6a64ad664eee2f5817e6157b5564f72ac3381cb7f0881e2ef73bab6b80c3a110", + "baselineSourceSha256": "6a64ad664eee2f5817e6157b5564f72ac3381cb7f0881e2ef73bab6b80c3a110", + "newlyBound": true, + "sourceBytesChanged": false + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", + "before": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50", + "after": "26aa70893a8182f191f9b4d02d0f68a04ec4da6017b83e1d4726a8327ee93781", + "baselineSourceSha256": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", + "before": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417", + "after": "c60650a74476af5faadd8cc76bcab4d073fd294dfac28d8dc176c07daeb7d4dc", + "baselineSourceSha256": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8", + "after": "fc2245d42e31f3426fef91d68bbe8039f67f50b0a47b9e60cd003c83d1776ad0", + "baselineSourceSha256": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", + "before": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d", + "after": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071", + "baselineSourceSha256": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiServiceCreateChange.java", + "before": null, + "after": "ac42a7cf1108ae29791ba3198c7602f5fdc4f786e5eb9cd278581fe23831be4f", + "baselineSourceSha256": null, + "newlyBound": true, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/util/PortBindingAddress.java", + "before": null, + "after": "e263eaaaeacc6bad7d09a9e21b48cafa098ced410857b8ab55dc1bab21ffccd4", + "baselineSourceSha256": "e263eaaaeacc6bad7d09a9e21b48cafa098ced410857b8ab55dc1bab21ffccd4", + "newlyBound": true, + "sourceBytesChanged": false + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/util/PortSpec.java", + "before": null, + "after": "66d88b6af51001ce9a49d0b47d9031c2fb0c076ff60b75b4f6b30473da64bd9e", + "baselineSourceSha256": "19c50738b291e64f425a05e58d781389cba151933d9741e9ab7f0b5a11a0d734", + "newlyBound": true, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ServiceValidationFilter.java", + "before": null, + "after": "39d55237e452e7dbec7122eed74bee22d01f4db79e0f5c70ea26c28509f83107", + "baselineSourceSha256": "57e69c59889ca0d9cb5956659755625f923e0f67af57d428471f0247b2683575", + "newlyBound": true, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/service/ServiceCreateValidation.java", + "before": null, + "after": "663e4a276a68636e4d5284cfb7b4fd80301d733f4a410775e4f6d7ae34e9a143", + "baselineSourceSha256": null, + "newlyBound": true, + "sourceBytesChanged": true + }, + { + "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/process/ServiceCreate.java", + "before": null, + "after": "4c04eed9b5cc6cab4cf46b3c165021c9d15c3feecaf418f44377261d4e415157", + "baselineSourceSha256": "4c04eed9b5cc6cab4cf46b3c165021c9d15c3feecaf418f44377261d4e415157", + "newlyBound": true, + "sourceBytesChanged": false + }, + { + "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/service/impl/ServiceDiscoveryServiceImpl.java", + "before": null, + "after": "0300fb72cd8f5a1a06c3d26509a9d74c79c644d1aab0c1ce16fa46357a8b1039", + "baselineSourceSha256": "0300fb72cd8f5a1a06c3d26509a9d74c79c644d1aab0c1ce16fa46357a8b1039", + "newlyBound": true, + "sourceBytesChanged": false + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "before": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85", + "after": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df", + "baselineSourceSha256": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e", + "after": "113f44df62f09f822b7f97f4cfdcab96de2f0f70995c821632f801c20134110c", + "baselineSourceSha256": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "resources/content/schema/project/project-auth.json", + "before": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59", + "after": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf", + "baselineSourceSha256": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59", + "newlyBound": false, + "sourceBytesChanged": true + }, + { + "path": "resources/content/schema/user/user-auth.json", + "before": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e", + "after": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091", + "baselineSourceSha256": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e", + "newlyBound": false, + "sourceBytesChanged": true } - ] + ], + "reviewManifest": "scripts/api-key-operation-review-0.183.366.json" }, "schemas": [ "resources/content/schema/base/aiServiceDescriptionUpdate.json", @@ -1461,153 +1629,223 @@ ], "addedTuples": [], "changedTuples": [], - "authorizationRationale": { - "typedCreate": "Only ports and the closed healthCheck subset enter the existing Service create adapter. Current actor/project/role/key max/revision/expiry/CAS, human approval, audit and one durable claim remain; environment and raw launchConfig remain rejected.", - "executionResult": "admin/project/user gain r only on a new read-only result field, never create/update. Existing receipt access still reauthorizes the actual same-account Service and pins ID/UUID. assignedPorts is emitted only after exact readback succeeds; approved changes remain immutable.", - "nativeOwners": "ServiceValidationFilter, PortSpec, PortPreflightInputs/Service, PortBindingAddress, ServiceCreate and ServiceDiscoveryServiceImpl are now source-bound alongside AiServiceCreateChange and ServiceCreateValidation. The same parser, preflight and persisted allocation owner remain authoritative.", - "operation": "1396 exact tuples and every derived semantic/registered/unsupported/human-only disposition are unchanged. Frozen v1 schemas and all existing role permissions are unchanged except the three explicit new r projection entries.", - "catalogProjection": "A fresh copy of InstanceRecord data.fields feeds the existing native role-schema projection with the same formatter/POST and non-create-response rules; it preserves already-authorized business values without introducing a new permission or transport.", - "acceptance": "Source extraction is not artifact, role-matrix, HTTP or asynchronous runtime acceptance; the original PR149/CI failure remains preserved." - }, "sourceInputChanges": [ { - "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/port/PortPreflightInputs.java", + "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/volume/VolumePreflightInputs.java", + "before": null, + "after": "dffdce7b133cf1bc16b410c32f365a6fee5ce807e79802a2b3d52a98da1dfc02", + "newlyBound": true, + "baselineSourceSha256": "dffdce7b133cf1bc16b410c32f365a6fee5ce807e79802a2b3d52a98da1dfc02" + }, + { + "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/volume/VolumePreflightService.java", "before": null, - "after": "237060330f0ae0c092b11018159b6bf9fa214e1e2564d57f6b139dee34c38e13", - "baselineSourceSha256": "237060330f0ae0c092b11018159b6bf9fa214e1e2564d57f6b139dee34c38e13", + "after": "4f2a93e0e00abb68277d09351a0a85c7fa201bd8e1d07e2bbbd8bcaaa5ce18e2", "newlyBound": true, - "sourceBytesChanged": false + "baselineSourceSha256": "4f2a93e0e00abb68277d09351a0a85c7fa201bd8e1d07e2bbbd8bcaaa5ce18e2" }, { - "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/port/PortPreflightService.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryInputHandler.java", "before": null, - "after": "6a64ad664eee2f5817e6157b5564f72ac3381cb7f0881e2ef73bab6b80c3a110", - "baselineSourceSha256": "6a64ad664eee2f5817e6157b5564f72ac3381cb7f0881e2ef73bab6b80c3a110", + "after": "46abd6c96d543f84b3884d1dafd4f41db9f6111e0edf9f770a8289f5604390a1", "newlyBound": true, - "sourceBytesChanged": false + "baselineSourceSha256": null }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", - "before": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50", - "after": "26aa70893a8182f191f9b4d02d0f68a04ec4da6017b83e1d4726a8327ee93781", - "baselineSourceSha256": "00945aae0ccd8091f6835168bd0d0ff7615be2f00cc86c27f48218520d680f50", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryService.java", + "before": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6", + "after": "01f316fb38aefd1e43f81773dab71576ff53ef37c4b8a9e844ae6d2556df3aca", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", - "before": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417", - "after": "c60650a74476af5faadd8cc76bcab4d073fd294dfac28d8dc176c07daeb7d4dc", - "baselineSourceSha256": "542f4d8b53b7360a4b72570466f63f027469d5bf2f0e3ce66ea3d6b6ceb6f417", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", + "before": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52", + "after": "5f0e1de8995318226e0c8ae48acbd3a6e07b025e7fa068daa1bc5945e25c6d49", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8", - "after": "fc2245d42e31f3426fef91d68bbe8039f67f50b0a47b9e60cd003c83d1776ad0", - "baselineSourceSha256": "141fa3773edceec3c7f489d10964ebc4db4c8ff4271976f6ee58ac0f342e96a8", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanResourceManager.java", + "before": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98", + "after": "b81b7e746d7557bd740b164123bd7584cf399b8b5a2bd57e258535392fe67b3d", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", - "before": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d", - "after": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071", - "baselineSourceSha256": "bfeb2e89f4a3049ec70292e14e29b2a74b4302f8756e80954edb2fdbe6497f0d", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanService.java", + "before": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3", + "after": "119c0347b3e6b984a76e1250fb2d2fee79337b77b13ed9dd87d888518efa818a", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiServiceCreateChange.java", - "before": null, - "after": "ac42a7cf1108ae29791ba3198c7602f5fdc4f786e5eb9cd278581fe23831be4f", - "baselineSourceSha256": null, - "newlyBound": true, - "sourceBytesChanged": true + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanResourceManager.java", + "before": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050", + "after": "17ba569ba4f5265d7d3a8e50cc3ce2d21d220232c354e169be511b74ce212b17", + "newlyBound": false, + "baselineSourceSha256": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050" }, { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/util/PortBindingAddress.java", - "before": null, - "after": "e263eaaaeacc6bad7d09a9e21b48cafa098ced410857b8ab55dc1bab21ffccd4", - "baselineSourceSha256": "e263eaaaeacc6bad7d09a9e21b48cafa098ced410857b8ab55dc1bab21ffccd4", - "newlyBound": true, - "sourceBytesChanged": false + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", + "before": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071", + "after": "1b920af86f0294b1c1e1ee44f4a9b7dc9bf7eac9e269298a1007c8ff235e6d45", + "newlyBound": false, + "baselineSourceSha256": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071" }, { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/util/PortSpec.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanInputHandler.java", "before": null, - "after": "66d88b6af51001ce9a49d0b47d9031c2fb0c076ff60b75b4f6b30473da64bd9e", - "baselineSourceSha256": "19c50738b291e64f425a05e58d781389cba151933d9741e9ab7f0b5a11a0d734", + "after": "a38c9fece7444a5fd5385b48f9b9ca2997e8218c82c91096473115cc87ee69aa", "newlyBound": true, - "sourceBytesChanged": true + "baselineSourceSha256": null }, { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ServiceValidationFilter.java", - "before": null, - "after": "39d55237e452e7dbec7122eed74bee22d01f4db79e0f5c70ea26c28509f83107", - "baselineSourceSha256": "57e69c59889ca0d9cb5956659755625f923e0f67af57d428471f0247b2683575", - "newlyBound": true, - "sourceBytesChanged": true + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanResourceManager.java", + "before": "51ac797f47ba68e52d360c6c53bb3aa143e33a637286deca9636f5d55559f1a8", + "after": "30cb1cdae0766c0f0a6c7f3f4c27187ca0bd565002bda4e311368e420e88d9a7", + "newlyBound": false, + "baselineSourceSha256": "51ac797f47ba68e52d360c6c53bb3aa143e33a637286deca9636f5d55559f1a8" }, { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/service/ServiceCreateValidation.java", + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanService.java", "before": null, - "after": "663e4a276a68636e4d5284cfb7b4fd80301d733f4a410775e4f6d7ae34e9a143", - "baselineSourceSha256": null, + "after": "7398ac81e4287ee2e950d2b86b8ce55fd574d8af40831044553514e729d8204d", "newlyBound": true, - "sourceBytesChanged": true + "baselineSourceSha256": "12782760bbd8e600c06876006e39ff9e7f3de818ffb8e39a5e294f9484579a8a" }, { - "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/process/ServiceCreate.java", - "before": null, - "after": "4c04eed9b5cc6cab4cf46b3c165021c9d15c3feecaf418f44377261d4e415157", - "baselineSourceSha256": "4c04eed9b5cc6cab4cf46b3c165021c9d15c3feecaf418f44377261d4e415157", - "newlyBound": true, - "sourceBytesChanged": false + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ServiceUpgradeValidationFilter.java", + "before": "27cea9ff5e7efc030de24d0095669fa5812a7931d64bcdbf8dfefb8888d7db36", + "after": "1adcf76557b3392161a46393d795b67a0ccc781a49d544fa8672aafe49475a63", + "newlyBound": false, + "baselineSourceSha256": "27cea9ff5e7efc030de24d0095669fa5812a7931d64bcdbf8dfefb8888d7db36" }, { - "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/service/impl/ServiceDiscoveryServiceImpl.java", + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/service/ServiceUpgradeValidation.java", "before": null, - "after": "0300fb72cd8f5a1a06c3d26509a9d74c79c644d1aab0c1ce16fa46357a8b1039", - "baselineSourceSha256": "0300fb72cd8f5a1a06c3d26509a9d74c79c644d1aab0c1ce16fa46357a8b1039", + "after": "ffcf91e17f0aaffe821acf297d1e125768a5332c062c1bf5c435114035255cf1", "newlyBound": true, - "sourceBytesChanged": false + "baselineSourceSha256": null + }, + { + "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/IaasApiConfig.java", + "before": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130", + "after": "fa64b48420abe59930c464283a773d2dfcccbaeea152fdf1a997d8dcae184e9a", + "newlyBound": false, + "baselineSourceSha256": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130" }, { "path": "resources/content/schema/admin/admin-auth.json", - "before": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85", - "after": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df", - "baselineSourceSha256": "27630fdb4d5cf7a1dcd7e693b84d0b31513d627a64a5d2365119075aca6d4f85", + "before": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df", + "after": "0282ac1900b37648214fc48dce394e00c1b22cc27340e08612d5e5e81ca66f64", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df" }, { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e", - "after": "113f44df62f09f822b7f97f4cfdcab96de2f0f70995c821632f801c20134110c", - "baselineSourceSha256": "7acb469f2ffbd0454e7e4e071aa672d3c444140583f41798a6d3cb02a632ba5e", + "path": "resources/content/schema/base/aiServiceUpgradePlan.json", + "before": "025cd4a0b044cda9f2500a79a99b605c867f95e8f69d46997ed843b221d67cb6", + "after": "5285ad2caab0e05e28fed27299e5ad114e84c15820bba9a9cfbfc45a163b62d0", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "025cd4a0b044cda9f2500a79a99b605c867f95e8f69d46997ed843b221d67cb6" }, { "path": "resources/content/schema/project/project-auth.json", - "before": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59", - "after": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf", - "baselineSourceSha256": "c752d35a5b2b2bdc65af5914d89071948e5b10e41772736ade7f89dfd2554a59", + "before": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf", + "after": "8ba8e18dca27a9cc65208c49a5ab72f3ff3d4d4353ae6b8d6891a2f913a76f09", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf" }, { "path": "resources/content/schema/user/user-auth.json", - "before": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e", - "after": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091", - "baselineSourceSha256": "0f96a7e195647b967d4f83cab74d6a0fc5c442a896baa54384937af765bc2d9e", + "before": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091", + "after": "4f54ea9c4b8b2d7d7e849e17a73f33d7262f2519e8263e28842ee7eaec414835", "newlyBound": false, - "sourceBytesChanged": true + "baselineSourceSha256": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091" + } + ], + "roleFieldDelta": [ + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", + "before": null, + "after": "r" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", + "before": null, + "after": "r" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", + "before": null, + "after": "r" } ], - "reviewManifest": "scripts/api-key-operation-review-0.183.366.json" + "authorizationRationale": { + "operation": "1396 exact tuples and each semantic/registered/unsupported/human-only disposition unchanged.", + "strategy": "Only existing upgrade-plan roles accept three optional scalar fields; dispatch still uses the original authorized native manager. No target or scope grant is added.", + "preflight": "Existing native filter implements the shared read-only interface. Native strategy invariants, including retain-IP/start-first, and the same port/volume rules run at preview/confirmation/apply; native batch normalization and original validation errors remain unchanged, including their canonical DENY reason.", + "audit": "Exact typed audit collection only; a sticky boolean retains the original closed-field rejection, and the existing owner audits ERROR without invoking its runner. No raw payload is retained.", + "acceptance": "Source extraction does not establish artifact, role matrix, HTTP/UI or asynchronous runtime acceptance.", + "nativeActionOperandLookup": "Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults." + } }, "schemas": [ "resources/content/schema/base/aiServiceDescriptionUpdate.json", @@ -1627,348 +1865,132 @@ "changedTuples": [], "sourceInputChanges": [ { - "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/volume/VolumePreflightInputs.java", - "before": null, - "after": "dffdce7b133cf1bc16b410c32f365a6fee5ce807e79802a2b3d52a98da1dfc02", - "newlyBound": true, - "baselineSourceSha256": "dffdce7b133cf1bc16b410c32f365a6fee5ce807e79802a2b3d52a98da1dfc02" - }, - { - "path": "code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/volume/VolumePreflightService.java", - "before": null, - "after": "4f2a93e0e00abb68277d09351a0a85c7fa201bd8e1d07e2bbbd8bcaaa5ce18e2", - "newlyBound": true, - "baselineSourceSha256": "4f2a93e0e00abb68277d09351a0a85c7fa201bd8e1d07e2bbbd8bcaaa5ce18e2" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryInputHandler.java", - "before": null, - "after": "46abd6c96d543f84b3884d1dafd4f41db9f6111e0edf9f770a8289f5604390a1", - "newlyBound": true, - "baselineSourceSha256": null - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/audit/AiAuditQueryService.java", - "before": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6", - "after": "01f316fb38aefd1e43f81773dab71576ff53ef37c4b8a9e844ae6d2556df3aca", - "newlyBound": false, - "baselineSourceSha256": "4067cc8685399b2b2bfb9e7b5eaddd10b906f63fccf286ef7e118622e4e80ea6" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", - "before": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52", - "after": "5f0e1de8995318226e0c8ae48acbd3a6e07b025e7fa068daa1bc5945e25c6d49", - "newlyBound": false, - "baselineSourceSha256": "639ceb2151b6668af77ff2f71ead2cd7699d76d45c17521e46a90af624286b52" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanResourceManager.java", - "before": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98", - "after": "b81b7e746d7557bd740b164123bd7584cf399b8b5a2bd57e258535392fe67b3d", - "newlyBound": false, - "baselineSourceSha256": "ef70bb6e029fdb446358ad81e9f791f14e892c28bda754b8ce479077fb85ff98" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchPlanService.java", - "before": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3", - "after": "119c0347b3e6b984a76e1250fb2d2fee79337b77b13ed9dd87d888518efa818a", - "newlyBound": false, - "baselineSourceSha256": "77b8f8b2b4ade110064ba0a874db04c1d5adbfe9df68655b8760c395270a05e3" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanResourceManager.java", - "before": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050", - "after": "17ba569ba4f5265d7d3a8e50cc3ce2d21d220232c354e169be511b74ce212b17", - "newlyBound": false, - "baselineSourceSha256": "37da81bdccdb528c1d7f1b18217d3896cabd0ad601e6a3809fd50913b6613050" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangePlanService.java", - "before": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071", - "after": "1b920af86f0294b1c1e1ee44f4a9b7dc9bf7eac9e269298a1007c8ff235e6d45", - "newlyBound": false, - "baselineSourceSha256": "1d52b18a6b3efc5f86b30e52cb1ab57bb27f788e28703a4e7864d473dd7aa071" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanInputHandler.java", - "before": null, - "after": "a38c9fece7444a5fd5385b48f9b9ca2997e8218c82c91096473115cc87ee69aa", - "newlyBound": true, - "baselineSourceSha256": null - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanResourceManager.java", - "before": "51ac797f47ba68e52d360c6c53bb3aa143e33a637286deca9636f5d55559f1a8", - "after": "30cb1cdae0766c0f0a6c7f3f4c27187ca0bd565002bda4e311368e420e88d9a7", - "newlyBound": false, - "baselineSourceSha256": "51ac797f47ba68e52d360c6c53bb3aa143e33a637286deca9636f5d55559f1a8" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/upgrade/AiServiceUpgradePlanService.java", - "before": null, - "after": "7398ac81e4287ee2e950d2b86b8ce55fd574d8af40831044553514e729d8204d", - "newlyBound": true, - "baselineSourceSha256": "12782760bbd8e600c06876006e39ff9e7f3de818ffb8e39a5e294f9484579a8a" - }, - { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/ServiceUpgradeValidationFilter.java", - "before": "27cea9ff5e7efc030de24d0095669fa5812a7931d64bcdbf8dfefb8888d7db36", - "after": "1adcf76557b3392161a46393d795b67a0ccc781a49d544fa8672aafe49475a63", - "newlyBound": false, - "baselineSourceSha256": "27cea9ff5e7efc030de24d0095669fa5812a7931d64bcdbf8dfefb8888d7db36" - }, - { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/service/ServiceUpgradeValidation.java", - "before": null, - "after": "ffcf91e17f0aaffe821acf297d1e125768a5332c062c1bf5c435114035255cf1", - "newlyBound": true, - "baselineSourceSha256": null - }, - { - "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/IaasApiConfig.java", - "before": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130", - "after": "fa64b48420abe59930c464283a773d2dfcccbaeea152fdf1a997d8dcae184e9a", - "newlyBound": false, - "baselineSourceSha256": "59b633a3f6614f811b5f14b0eeeeb1f99683d319a2c28bea2093f48a33a53130" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "before": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df", - "after": "0282ac1900b37648214fc48dce394e00c1b22cc27340e08612d5e5e81ca66f64", - "newlyBound": false, - "baselineSourceSha256": "f713ba64c53fb498b2cbe7f96b5c794ed35d093a74a78cafcffe3f19b59ef5df" - }, - { - "path": "resources/content/schema/base/aiServiceUpgradePlan.json", - "before": "025cd4a0b044cda9f2500a79a99b605c867f95e8f69d46997ed843b221d67cb6", - "after": "5285ad2caab0e05e28fed27299e5ad114e84c15820bba9a9cfbfc45a163b62d0", - "newlyBound": false, - "baselineSourceSha256": "025cd4a0b044cda9f2500a79a99b605c867f95e8f69d46997ed843b221d67cb6" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "before": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf", - "after": "8ba8e18dca27a9cc65208c49a5ab72f3ff3d4d4353ae6b8d6891a2f913a76f09", - "newlyBound": false, - "baselineSourceSha256": "ce1ebde019bfd57d6ddd5c2b77d119a76e1df38bf887371e765478c3d96388cf" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "before": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091", - "after": "4f54ea9c4b8b2d7d7e849e17a73f33d7262f2519e8263e28842ee7eaec414835", - "newlyBound": false, - "baselineSourceSha256": "66a82fa448806fb826ecd7a11bc9dc6cc9598e7a221d63191113457fd3376091" - } - ], - "roleFieldDelta": [ - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiExternalServiceChange.java", + "before": "d9497a9c37842c09e833d434e5f361585f721a72eff9976c5b66902181f392e5", + "after": "4475ebc7215960f69ebaf5c5c925a3c2f4b933975efb9205aba16f80d30eaf47" }, { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", + "before": "c60650a74476af5faadd8cc76bcab4d073fd294dfac28d8dc176c07daeb7d4dc", + "after": "8b81eaa0446c07c786e588ae8299456bc3c25dca90cf31810c29d1ca5a838e35" }, { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "fc2245d42e31f3426fef91d68bbe8039f67f50b0a47b9e60cd003c83d1776ad0", + "after": "331c7fa5eae1f60d4f33e3f3767c546d684042be6156a756ab60c627feef443a" } ], + "roleFieldDelta": [], "authorizationRationale": { - "operation": "1396 exact tuples and each semantic/registered/unsupported/human-only disposition unchanged.", - "strategy": "Only existing upgrade-plan roles accept three optional scalar fields; dispatch still uses the original authorized native manager. No target or scope grant is added.", - "preflight": "Existing native filter implements the shared read-only interface. Native strategy invariants, including retain-IP/start-first, and the same port/volume rules run at preview/confirmation/apply; native batch normalization and original validation errors remain unchanged, including their canonical DENY reason.", - "audit": "Exact typed audit collection only; a sticky boolean retains the original closed-field rejection, and the existing owner audits ERROR without invoking its runner. No raw payload is retained.", - "acceptance": "Source extraction does not establish artifact, role matrix, HTTP/UI or asynchronous runtime acceptance.", - "nativeActionOperandLookup": "Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults." + "externalServiceCreate": "Existing parent/owner/current role/Key/revision, native schema and single durable claim boundaries remain; native cr field only, no update or global grant." } }, - "schemas": [ - "resources/content/schema/base/aiServiceDescriptionUpdate.json", - "resources/content/schema/base/aiServiceUpgradePlan.json", - "resources/content/schema/base/aiToolCallEvent.json", - "resources/content/schema/base/aiResourceView.json", - "resources/content/schema/base/aiResourceChangePlan.json", - "resources/content/schema/base/processPool.json", - "resources/content/schema/base/aiCatalogMetadataView.json", - "resources/content/schema/base/aiCatalogStackLaunchOptions.json", - "resources/content/schema/base/aiCatalogStackLaunchPlan.json", - "resources/content/schema/base/aiCatalogStackLaunchInputs.json", - "resources/content/schema/base/aiAuditQuery.json", - "resources/content/schema/base/aiAuditSourcePageInput.json" - ], - "addedTuples": [], - "changedTuples": [], "sourceInputChanges": [ { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiExternalServiceChange.java", - "before": "d9497a9c37842c09e833d434e5f361585f721a72eff9976c5b66902181f392e5", - "after": "4475ebc7215960f69ebaf5c5c925a3c2f4b933975efb9205aba16f80d30eaf47" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewService.java", + "before": "54f9aeaeecbf135e34210417f5cf7ec128a90e05b1971fc351ed4bdd76bf8585", + "after": "19d91fa89474c9c9604ff7a688afd9a36e6d75fd313667f16ef5550efedc050b" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", - "before": "c60650a74476af5faadd8cc76bcab4d073fd294dfac28d8dc176c07daeb7d4dc", - "after": "8b81eaa0446c07c786e588ae8299456bc3c25dca90cf31810c29d1ca5a838e35" + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/VolumeDao.java", + "before": null, + "after": "8fbeb7ef1b63fa000d1788e2603d39de5e6e7dd83eec19df126bcb84510d3823" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": "fc2245d42e31f3426fef91d68bbe8039f67f50b0a47b9e60cd003c83d1776ad0", - "after": "331c7fa5eae1f60d4f33e3f3767c546d684042be6156a756ab60c627feef443a" + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/VolumeDaoImpl.java", + "before": null, + "after": "b0a41797307b7a02d44ad5ac08f00bad3fa21fb78143825ac4a01774a8ddcfe6" } - ], - "roleFieldDelta": [], - "authorizationRationale": { - "externalServiceCreate": "Existing parent/owner/current role/Key/revision, native schema and single durable claim boundaries remain; native cr field only, no update or global grant." - } + ] }, "sourceInputChanges": [ + { + "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", + "before": "cb6bb0a8ed3dda48bedd3ae89ed78a490f644c1040c5aa16463e1e701250988e", + "after": "df2b080903d9ee35ccb89c0970adbf5b6c88d46fc5a6401eecb620a984b739c4" + }, + { + "path": "code/framework/object/src/main/java/io/cattle/platform/object/impl/AbstractObjectManager.java", + "before": null, + "after": "db0328ff05701ca146e2f770d9bcb34af1d91a73a3e4d59ae432a2c749649539" + }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewService.java", - "before": "54f9aeaeecbf135e34210417f5cf7ec128a90e05b1971fc351ed4bdd76bf8585", - "after": "19d91fa89474c9c9604ff7a688afd9a36e6d75fd313667f16ef5550efedc050b" + "before": "19d91fa89474c9c9604ff7a688afd9a36e6d75fd313667f16ef5550efedc050b", + "after": "9343029c4b9022380b5b03b434d0d42167d17884b0b54dd868c2fa826ad100ea" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChange.java", + "before": null, + "after": "e08978156bc77733326da1d299c97038103049d441a41f0332e3115b4ac07ce7" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", + "before": "8b81eaa0446c07c786e588ae8299456bc3c25dca90cf31810c29d1ca5a838e35", + "after": "dd89c69f6f0608176ca8b6daac85f408c572932b434452abddee233b31f77f96" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "331c7fa5eae1f60d4f33e3f3767c546d684042be6156a756ab60c627feef443a", + "after": "2378a8875ae2a2b76e70d7c57a0af715d02f6165228bc2063139f208298669d7" + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/HostDao.java", + "before": null, + "after": "eace82dc713adf92ea4d5b5f0f19db96ad978528956b6abce249b3bf436a25ad" + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/ServiceDao.java", + "before": null, + "after": "92a4cd76c988cfab8f3ee3b8e96f2f2ef5fa06c2787ec46ca03cb75ec4f2a7ce" + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/HostDaoImpl.java", + "before": null, + "after": "ae0fff79276ce84e8ab5ec19b80b09a78f7c91740c24706e6129be0f15f0f483" + }, + { + "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/ServiceDaoImpl.java", + "before": null, + "after": "d13a7164f03e71e7fbbedd9165667834b6f33f99607500d2d819ceecab75902b" + }, + { + "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/dao/ServiceDao.java", + "before": null, + "after": "68efa2475c42844de04d61ec821ea877cea9fdbd7da1600ab2a9dea954a66c93" }, { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/VolumeDao.java", + "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/dao/impl/ServiceDaoImpl.java", "before": null, - "after": "8fbeb7ef1b63fa000d1788e2603d39de5e6e7dd83eec19df126bcb84510d3823" + "after": "05cb4a7254436ddf8583984274d7c1e1567eb6e98d33ba00d17dba24ea312c86" }, { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/VolumeDaoImpl.java", + "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/SystemServicesConfig.java", "before": null, - "after": "b0a41797307b7a02d44ad5ac08f00bad3fa21fb78143825ac4a01774a8ddcfe6" + "after": "32dba774946a48c2d6a9ec9030fcb5b2ca324ba98cd3269478aec237984a75a8" + }, + { + "path": "resources/content/schema/base/aiResourceChangePlan.json", + "before": "113f44df62f09f822b7f97f4cfdcab96de2f0f70995c821632f801c20134110c", + "after": "b1d67ec8bd3a48b5d9768e6ac15ea92a3ed4203b1bedf512eccbcb7935192baf" } ] }, "sourceInputChanges": [ { - "path": "code/framework/auditing/src/main/java/io/cattle/platform/iaas/api/auditing/AuditServiceImpl.java", - "before": "cb6bb0a8ed3dda48bedd3ae89ed78a490f644c1040c5aa16463e1e701250988e", - "after": "df2b080903d9ee35ccb89c0970adbf5b6c88d46fc5a6401eecb620a984b739c4" - }, - { - "path": "code/framework/object/src/main/java/io/cattle/platform/object/impl/AbstractObjectManager.java", - "before": null, - "after": "db0328ff05701ca146e2f770d9bcb34af1d91a73a3e4d59ae432a2c749649539" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/view/AiResourceViewService.java", - "before": "19d91fa89474c9c9604ff7a688afd9a36e6d75fd313667f16ef5550efedc050b", - "after": "9343029c4b9022380b5b03b434d0d42167d17884b0b54dd868c2fa826ad100ea" - }, - { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiHostMetadataChange.java", - "before": null, - "after": "e08978156bc77733326da1d299c97038103049d441a41f0332e3115b4ac07ce7" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", + "before": "5f0e1de8995318226e0c8ae48acbd3a6e07b025e7fa068daa1bc5945e25c6d49", + "after": "18c9a38db699f6f0413a2c6601971ec7b7f753e1e8c26f54331df53562ecc782" }, { - "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDescriptor.java", - "before": "8b81eaa0446c07c786e588ae8299456bc3c25dca90cf31810c29d1ca5a838e35", - "after": "dd89c69f6f0608176ca8b6daac85f408c572932b434452abddee233b31f77f96" + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java", + "before": "e75f911701471e63f85b70df645040a4142704f8da94f3008ed2187c43b35a1d", + "after": "27f232a3190210e1a34c158bd379c71d6356e39fbca58a069b2fc0378f91bdd2" }, { "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", - "before": "331c7fa5eae1f60d4f33e3f3767c546d684042be6156a756ab60c627feef443a", - "after": "2378a8875ae2a2b76e70d7c57a0af715d02f6165228bc2063139f208298669d7" - }, - { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/HostDao.java", - "before": null, - "after": "eace82dc713adf92ea4d5b5f0f19db96ad978528956b6abce249b3bf436a25ad" - }, - { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/ServiceDao.java", - "before": null, - "after": "92a4cd76c988cfab8f3ee3b8e96f2f2ef5fa06c2787ec46ca03cb75ec4f2a7ce" - }, - { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/HostDaoImpl.java", - "before": null, - "after": "ae0fff79276ce84e8ab5ec19b80b09a78f7c91740c24706e6129be0f15f0f483" - }, - { - "path": "code/iaas/model/src/main/java/io/cattle/platform/core/dao/impl/ServiceDaoImpl.java", - "before": null, - "after": "d13a7164f03e71e7fbbedd9165667834b6f33f99607500d2d819ceecab75902b" - }, - { - "path": "code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/dao/ServiceDao.java", - "before": null, - "after": "68efa2475c42844de04d61ec821ea877cea9fdbd7da1600ab2a9dea954a66c93" - }, - { - "path": "code/iaas/service-discovery/server/src/main/java/io/cattle/platform/servicediscovery/dao/impl/ServiceDaoImpl.java", - "before": null, - "after": "05cb4a7254436ddf8583984274d7c1e1567eb6e98d33ba00d17dba24ea312c86" - }, - { - "path": "code/packaging/app-config/src/main/java/io/cattle/platform/app/SystemServicesConfig.java", - "before": null, - "after": "32dba774946a48c2d6a9ec9030fcb5b2ca324ba98cd3269478aec237984a75a8" - }, - { - "path": "resources/content/schema/base/aiResourceChangePlan.json", - "before": "113f44df62f09f822b7f97f4cfdcab96de2f0f70995c821632f801c20134110c", - "after": "b1d67ec8bd3a48b5d9768e6ac15ea92a3ed4203b1bedf512eccbcb7935192baf" + "before": "2378a8875ae2a2b76e70d7c57a0af715d02f6165228bc2063139f208298669d7", + "after": "e95bbe9a62c4f7b19be4d72973669794d219654f8787b931456bf09fe38193b7" } ] }, diff --git a/scripts/api-key-operation-inventory.json b/scripts/api-key-operation-inventory.json index 39a7516674..f666819add 100644 --- a/scripts/api-key-operation-inventory.json +++ b/scripts/api-key-operation-inventory.json @@ -220,17 +220,17 @@ "defaultProcesses": "ebf41ab873c349656c307511712674e0db434a9c4986e08d03ec35d92bdcd6a7" }, "currentSourceReview": { - "version": "unreleased-370-host-metadata-readable-workloads-and-catalog-binding", + "version": "unreleased-371-native-host-model-and-catalog-version-bootstrap", "status": "SOURCE_REEXTRACTED_NOT_ARTIFACT_OR_RUNTIME_ACCEPTANCE", - "baseCommit": "de1372f27117900a8940783efdaa0e90edeb415b", + "baseCommit": "7f48482e17bec0eab1010fb045e1771b2b695caf", "sourceCandidate": "WORKTREE_NOT_COMMITTED", "producerCommit": "NOT_ASSIGNED_DURING_SOURCE_REVIEW", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "55ad6de95049436b43bcdc55ba910cdc09634cb7845a588980d4749fa945832b", + "contractSha256": "a01c913e43148af9044ce500e4754028e6a2e2b1dad6e79a2d8646a47da721f1", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 458, - "sha256": "7082897c9cfe9d80534ac02a871e0ef4e79356f1c23a618aadd8616be89c3069", + "sha256": "59ec478abbedf97403a9615c80785b9f69fc289b703650b8316c03730c1244f7", "anchors": [ "AbstractObjectManager.java", "AbstractSchemaFactory.java", @@ -358,18 +358,19 @@ "roleFieldDelta": [], "historicalInventoryPreserved": true, "previousReview": { - "version": "unreleased-369-volume-usage-safe-read", + "version": "unreleased-370-host-metadata-readable-workloads-and-catalog-binding", "status": "SOURCE_REEXTRACTED_NOT_ARTIFACT_OR_RUNTIME_ACCEPTANCE", - "baseCommit": "f466d3e514cc08f1d826299826b0b5db27df5d2f", + "baseCommit": "de1372f27117900a8940783efdaa0e90edeb415b", "sourceCandidate": "WORKTREE_NOT_COMMITTED", "producerCommit": "NOT_ASSIGNED_DURING_SOURCE_REVIEW", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "99aba456727ef358339aa9456aa711e36aa9d72448cf10886a665892b1e2d048", + "contractSha256": "55ad6de95049436b43bcdc55ba910cdc09634cb7845a588980d4749fa945832b", "sourceInputs": { "algorithm": "c03-source-inputs-v1", - "count": 449, - "sha256": "5bbfd304067c39be44f7415ed00e654e2386ef8827f618a2f13ef6ed1c7562a3", + "count": 458, + "sha256": "7082897c9cfe9d80534ac02a871e0ef4e79356f1c23a618aadd8616be89c3069", "anchors": [ + "AbstractObjectManager.java", "AbstractSchemaFactory.java", "AiAliasServiceChange.java", "AiAuditQuery.java", @@ -409,6 +410,7 @@ "AiCatalogLaunchRunner.java", "AiCatalogLaunchWorkflowTest.java", "AiExternalServiceChange.java", + "AiHostMetadataChange.java", "AiResourceChangeDescriptor.java", "AiResourceChangeDomain.java", "AiResourceChangePlanService.java", @@ -443,6 +445,8 @@ "ExternalDynamicExtensionHandler.java", "FileSchemaFactory.java", "GenericWhitelistedProxy.java", + "HostDao.java", + "HostDaoImpl.java", "IaasApiConfig.java", "InServiceUpgradeStrategy.java", "NotWritablePostProcessor.java", @@ -458,11 +462,14 @@ "ServiceConsumeMapDaoImpl.java", "ServiceCreate.java", "ServiceCreateValidation.java", + "ServiceDao.java", + "ServiceDaoImpl.java", "ServiceDiscoveryServiceImpl.java", "ServiceUpgradeStrategy.java", "ServiceUpgradeValidation.java", "ServiceUpgradeValidationFilter.java", "ServiceValidationFilter.java", + "SystemServicesConfig.java", "TokenAuthLookup.java", "TransformInspect.java", "VolumeDao.java", @@ -489,18 +496,17 @@ "roleFieldDelta": [], "historicalInventoryPreserved": true, "previousReview": { - "version": "unreleased-368-external-service-create-tcp-healthcheck", + "version": "unreleased-369-volume-usage-safe-read", "status": "SOURCE_REEXTRACTED_NOT_ARTIFACT_OR_RUNTIME_ACCEPTANCE", - "baseCommit": "8678dec7dd261c2f2b68f329f0722d8edc91ac1f", - "functionalSourceCommit": "2bd0ed5abffd3413422916b1df1aeaecb86fcb6a", + "baseCommit": "f466d3e514cc08f1d826299826b0b5db27df5d2f", "sourceCandidate": "WORKTREE_NOT_COMMITTED", "producerCommit": "NOT_ASSIGNED_DURING_SOURCE_REVIEW", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "0fb84661147a50f27e51a8a9501ac52fb0d3cabac02e790d6522dfe8c556e5dd", + "contractSha256": "99aba456727ef358339aa9456aa711e36aa9d72448cf10886a665892b1e2d048", "sourceInputs": { "algorithm": "c03-source-inputs-v1", - "count": 447, - "sha256": "da122b39e8ffac069b78d60c7234ff62cbb1995028efa835a7f717f8d02dee4c", + "count": 449, + "sha256": "5bbfd304067c39be44f7415ed00e654e2386ef8827f618a2f13ef6ed1c7562a3", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -597,6 +603,8 @@ "ServiceValidationFilter.java", "TokenAuthLookup.java", "TransformInspect.java", + "VolumeDao.java", + "VolumeDaoImpl.java", "VolumePreflightInputs.java", "VolumePreflightService.java" ], @@ -619,17 +627,18 @@ "roleFieldDelta": [], "historicalInventoryPreserved": true, "previousReview": { - "version": "unreleased-367-service-upgrade-strategy-audit-preview-native-action-lock-order-and-rollback-fence", - "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "c8cd1f8218d29e034c2569af1e0e8530c305ce50", + "version": "unreleased-368-external-service-create-tcp-healthcheck", + "status": "SOURCE_REEXTRACTED_NOT_ARTIFACT_OR_RUNTIME_ACCEPTANCE", + "baseCommit": "8678dec7dd261c2f2b68f329f0722d8edc91ac1f", + "functionalSourceCommit": "2bd0ed5abffd3413422916b1df1aeaecb86fcb6a", "sourceCandidate": "WORKTREE_NOT_COMMITTED", "producerCommit": "NOT_ASSIGNED_DURING_SOURCE_REVIEW", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "35b2b2447c0f07205b806ff7304aacb4a90ecface2558701dbde8725a56c50c8", + "contractSha256": "0fb84661147a50f27e51a8a9501ac52fb0d3cabac02e790d6522dfe8c556e5dd", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 447, - "sha256": "66a5ce2ee5f4277993c03de0f72a08dd828ec65e4c447430248c1b093583ab59", + "sha256": "da122b39e8ffac069b78d60c7234ff62cbb1995028efa835a7f717f8d02dee4c", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -745,96 +754,25 @@ "addedTuples": [], "removedTuples": [], "changedTuples": [], - "roleFieldDelta": [ - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.startFirst", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.batchSize", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", - "before": null, - "after": "cr" - }, - { - "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", - "before": null, - "after": "r" - } - ], + "roleFieldDelta": [], "historicalInventoryPreserved": true, "previousReview": { - "version": "unreleased-366-service-create-native-ports-healthcheck-and-catalog-projection", + "version": "unreleased-367-service-upgrade-strategy-audit-preview-native-action-lock-order-and-rollback-fence", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "259ef59e8110fab71488d5dbd8c9affda4279217", - "sourceCandidate": "9efe87becb752287072e2257c949680fed75282d", + "baseCommit": "c8cd1f8218d29e034c2569af1e0e8530c305ce50", + "sourceCandidate": "WORKTREE_NOT_COMMITTED", + "producerCommit": "NOT_ASSIGNED_DURING_SOURCE_REVIEW", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "8af458ac13a30c611fde008992297ad77b6fb40e237d615b7397e74e4302c820", + "contractSha256": "35b2b2447c0f07205b806ff7304aacb4a90ecface2558701dbde8725a56c50c8", "sourceInputs": { "algorithm": "c03-source-inputs-v1", - "count": 441, - "sha256": "7ce17c8b05ccfba961414808148d8638bf54a9ad3bbc7de00facfcba4b26ac39", + "count": 447, + "sha256": "66a5ce2ee5f4277993c03de0f72a08dd828ec65e4c447430248c1b093583ab59", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", "AiAuditQuery.java", + "AiAuditQueryInputHandler.java", "AiAuditQueryResourceManager.java", "AiAuditQueryRunner.java", "AiAuditQueryService.java", @@ -878,6 +816,8 @@ "AiResourceViewResourceManager.java", "AiResourceViewService.java", "AiServiceCreateChange.java", + "AiServiceUpgradePlanInputHandler.java", + "AiServiceUpgradePlanService.java", "ApiAuditPayload.java", "ApiAuthenticator.java", "ApiCookieAuthentication.java", @@ -903,6 +843,7 @@ "FileSchemaFactory.java", "GenericWhitelistedProxy.java", "IaasApiConfig.java", + "InServiceUpgradeStrategy.java", "NotWritablePostProcessor.java", "PortBindingAddress.java", "PortPreflightInputs.java", @@ -917,9 +858,14 @@ "ServiceCreate.java", "ServiceCreateValidation.java", "ServiceDiscoveryServiceImpl.java", + "ServiceUpgradeStrategy.java", + "ServiceUpgradeValidation.java", + "ServiceUpgradeValidationFilter.java", "ServiceValidationFilter.java", "TokenAuthLookup.java", - "TransformInspect.java" + "TransformInspect.java", + "VolumePreflightInputs.java", + "VolumePreflightService.java" ], "markers": [ "@Type", @@ -937,68 +883,92 @@ "addedTuples": [], "removedTuples": [], "changedTuples": [], - "roleProjectionDelta": [ + "roleFieldDelta": [ + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, { "path": "resources/content/schema/admin/admin-auth.json", - "pointer": "/authorize/aiResourceChangePlan.executionResult", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", "before": null, "after": "r" }, { "path": "resources/content/schema/project/project-auth.json", - "pointer": "/authorize/aiResourceChangePlan.executionResult", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", "before": null, "after": "r" }, { "path": "resources/content/schema/user/user-auth.json", - "pointer": "/authorize/aiResourceChangePlan.executionResult", + "pointer": "/authorize/aiServiceUpgradePlan.startFirst", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.batchSize", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.intervalMillis", + "before": null, + "after": "cr" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiServiceUpgradePlan.upgradeStrategy", "before": null, "after": "r" } ], - "materializerDynamicProjection": { - "source": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", - "rootCause": "DockerTransformer stores imageUuid/environment/ports/logConfig in InstanceRecord data.fields; the five-argument WrappedResource constructor did not receive that dynamic value map, so the real role-schema projection omitted required business values.", - "fix": "Pass a fresh LinkedHashMap copy of DataUtils.getFields(instance) to the seven-argument WrappedResource constructor. Keep the existing current-role SchemaFactory, ID formatter, POST and createResponse=false behavior.", - "preservedBusinessValues": [ - "native docker imageUuid", - "environment exact scalar including embedded equals", - "fixed native ports", - "logConfig driver/config", - "bean-backed service name" - ], - "visibilityBoundary": "Current native role schema remains authoritative. Hidden environment, read-on-create-only ports/logConfig and unlisted dynamic fields remain absent; no role or Key authority expansion, raw bypass or new environment-input channel.", - "focusedTests": { - "newMethods": [ - "dynamicProjectionKeepsRestrictedSchemaAndNonCreateResponseRules", - "realTransformerDynamicFieldsReachNativeRoleSchemaProjection" - ], - "existingAffectedMethods": [ - "preparedGraphSeparatesNativeMutationFromAuthorizedPublicReferenceIds" - ], - "tests": 3, - "failures": 0, - "errors": 0, - "skipped": 0, - "evidenceFile": "engine366-materializer-dynamic-fields-focused-20261102.json", - "evidenceSha256": "24ea39c3caf6f6664735e67308246db34a07c59f384cccea48d9e2b171cbd35a", - "usesRealDockerTransformerAndNativeSchemas": true - }, - "runtimeAcceptance": "PENDING_MATCHING_ARTIFACT_AND_NATIVE_QA; no live pass claimed" - }, "historicalInventoryPreserved": true, - "producerCommit": "NOT_ASSIGNED_UNSIGNED_C03_REVIEW", "previousReview": { - "version": "unreleased-365-catalog-private-native-wire-fix", + "version": "unreleased-366-service-create-native-ports-healthcheck-and-catalog-projection", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "aae91b38e1448ee4739cd4d361480e3fe633d925", + "baseCommit": "259ef59e8110fab71488d5dbd8c9affda4279217", + "sourceCandidate": "9efe87becb752287072e2257c949680fed75282d", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "fe3148ca74f9373fddc617be7ee518ac4d3618e38eed4038c227cb440c6347f1", + "contractSha256": "8af458ac13a30c611fde008992297ad77b6fb40e237d615b7397e74e4302c820", "sourceInputs": { "algorithm": "c03-source-inputs-v1", - "count": 432, - "sha256": "fdf53022efdab0e56edb292da037216be051643770565c090424124be8b115ea", + "count": 441, + "sha256": "7ce17c8b05ccfba961414808148d8638bf54a9ad3bbc7de00facfcba4b26ac39", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1045,6 +1015,7 @@ "AiResourceChangeProcessObserver.java", "AiResourceViewResourceManager.java", "AiResourceViewService.java", + "AiServiceCreateChange.java", "ApiAuditPayload.java", "ApiAuthenticator.java", "ApiCookieAuthentication.java", @@ -1071,12 +1042,20 @@ "GenericWhitelistedProxy.java", "IaasApiConfig.java", "NotWritablePostProcessor.java", + "PortBindingAddress.java", + "PortPreflightInputs.java", + "PortPreflightService.java", + "PortSpec.java", "ProcessAuthorization.java", "ProcessEventDecorator.java", "RequestReRouterHandler.java", "ResourceManagerRequestHandler.java", "SchemaFactoryImpl.java", "ServiceConsumeMapDaoImpl.java", + "ServiceCreate.java", + "ServiceCreateValidation.java", + "ServiceDiscoveryServiceImpl.java", + "ServiceValidationFilter.java", "TokenAuthLookup.java", "TransformInspect.java" ], @@ -1096,18 +1075,68 @@ "addedTuples": [], "removedTuples": [], "changedTuples": [], + "roleProjectionDelta": [ + { + "path": "resources/content/schema/admin/admin-auth.json", + "pointer": "/authorize/aiResourceChangePlan.executionResult", + "before": null, + "after": "r" + }, + { + "path": "resources/content/schema/project/project-auth.json", + "pointer": "/authorize/aiResourceChangePlan.executionResult", + "before": null, + "after": "r" + }, + { + "path": "resources/content/schema/user/user-auth.json", + "pointer": "/authorize/aiResourceChangePlan.executionResult", + "before": null, + "after": "r" + } + ], + "materializerDynamicProjection": { + "source": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchMaterializer.java", + "rootCause": "DockerTransformer stores imageUuid/environment/ports/logConfig in InstanceRecord data.fields; the five-argument WrappedResource constructor did not receive that dynamic value map, so the real role-schema projection omitted required business values.", + "fix": "Pass a fresh LinkedHashMap copy of DataUtils.getFields(instance) to the seven-argument WrappedResource constructor. Keep the existing current-role SchemaFactory, ID formatter, POST and createResponse=false behavior.", + "preservedBusinessValues": [ + "native docker imageUuid", + "environment exact scalar including embedded equals", + "fixed native ports", + "logConfig driver/config", + "bean-backed service name" + ], + "visibilityBoundary": "Current native role schema remains authoritative. Hidden environment, read-on-create-only ports/logConfig and unlisted dynamic fields remain absent; no role or Key authority expansion, raw bypass or new environment-input channel.", + "focusedTests": { + "newMethods": [ + "dynamicProjectionKeepsRestrictedSchemaAndNonCreateResponseRules", + "realTransformerDynamicFieldsReachNativeRoleSchemaProjection" + ], + "existingAffectedMethods": [ + "preparedGraphSeparatesNativeMutationFromAuthorizedPublicReferenceIds" + ], + "tests": 3, + "failures": 0, + "errors": 0, + "skipped": 0, + "evidenceFile": "engine366-materializer-dynamic-fields-focused-20261102.json", + "evidenceSha256": "24ea39c3caf6f6664735e67308246db34a07c59f384cccea48d9e2b171cbd35a", + "usesRealDockerTransformerAndNativeSchemas": true + }, + "runtimeAcceptance": "PENDING_MATCHING_ARTIFACT_AND_NATIVE_QA; no live pass claimed" + }, "historicalInventoryPreserved": true, - "producerCommit": "NOT_ASSIGNED_UNSIGNED_OVERLAY", + "producerCommit": "NOT_ASSIGNED_UNSIGNED_C03_REVIEW", "previousReview": { - "version": "unreleased-365-native-validation-cleanup-fixes", + "version": "unreleased-365-catalog-private-native-wire-fix", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "a335cf8513d8ddb61d1e90f19a9a70769b8ede04", + "baseCommit": "aae91b38e1448ee4739cd4d361480e3fe633d925", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "a1b07004f7e6d40c91c60a7cf88bab15d99ea5602f5347333717027098946bcb", + "contractSha256": "fe3148ca74f9373fddc617be7ee518ac4d3618e38eed4038c227cb440c6347f1", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 432, - "sha256": "5a8f5d8159d22c989733c92568916e936915a7d5a81724e3f082af3b65523f43", + "sha256": "fdf53022efdab0e56edb292da037216be051643770565c090424124be8b115ea", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1206,17 +1235,17 @@ "removedTuples": [], "changedTuples": [], "historicalInventoryPreserved": true, - "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE", + "producerCommit": "NOT_ASSIGNED_UNSIGNED_OVERLAY", "previousReview": { - "version": "unreleased-364-native-lifecycle-fixes", + "version": "unreleased-365-native-validation-cleanup-fixes", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "fcc23550a937f473b92e3adf5014bc3fa1096aef", + "baseCommit": "a335cf8513d8ddb61d1e90f19a9a70769b8ede04", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "0456695780b3b53bdbd034dd640dc21776b0242b5828f2aff56ab230eb722c6a", + "contractSha256": "a1b07004f7e6d40c91c60a7cf88bab15d99ea5602f5347333717027098946bcb", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 432, - "sha256": "169fe350bed9358c2d9cbdac24f75c8636258325f2a92305507ad06e79c993e1", + "sha256": "5a8f5d8159d22c989733c92568916e936915a7d5a81724e3f082af3b65523f43", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1317,15 +1346,15 @@ "historicalInventoryPreserved": true, "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE", "previousReview": { - "version": "unreleased-363-external-review-before", + "version": "unreleased-364-native-lifecycle-fixes", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "4c69935fa8177e9f9ae675cdd00702a45be72f33", + "baseCommit": "fcc23550a937f473b92e3adf5014bc3fa1096aef", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "1d0f71fa4913f3725b076ad0675f804e6297af9c7ef4a1036fd4acabd10da2c7", + "contractSha256": "0456695780b3b53bdbd034dd640dc21776b0242b5828f2aff56ab230eb722c6a", "sourceInputs": { "algorithm": "c03-source-inputs-v1", - "count": 431, - "sha256": "5bd56f773b6d57827ee04cfb5fb51f3e9ff95969a152edbbbff40abf3bb338fc", + "count": 432, + "sha256": "169fe350bed9358c2d9cbdac24f75c8636258325f2a92305507ad06e79c993e1", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1392,6 +1421,7 @@ "EngineContext.java", "EventBasedProcessHandler.java", "EventDiagnostics.java", + "ExternalDeploymentUnitInstance.java", "ExternalDynamicExtensionHandler.java", "FileSchemaFactory.java", "GenericWhitelistedProxy.java", @@ -1425,15 +1455,15 @@ "historicalInventoryPreserved": true, "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE", "previousReview": { - "version": "unreleased-362-service-index-cleanup", + "version": "unreleased-363-external-review-before", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "06d5bf0dac57bf3be63fa7ce868c8d5fec2508ce", + "baseCommit": "4c69935fa8177e9f9ae675cdd00702a45be72f33", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "0c28b642db338e44a8fac584f5502d77b18f591c31e5a28ea744afc850a114fd", + "contractSha256": "1d0f71fa4913f3725b076ad0675f804e6297af9c7ef4a1036fd4acabd10da2c7", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 431, - "sha256": "28e69f831778a7355fbcdbcb66a6af18467a15404861492fb4baa57ca64c5048", + "sha256": "5bd56f773b6d57827ee04cfb5fb51f3e9ff95969a152edbbbff40abf3bb338fc", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1533,14 +1563,15 @@ "historicalInventoryPreserved": true, "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE", "previousReview": { + "version": "unreleased-362-service-index-cleanup", "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", - "baseCommit": "48c052d569364e962938f1d2b6442330456113c3", + "baseCommit": "06d5bf0dac57bf3be63fa7ce868c8d5fec2508ce", "contract": "scripts/api-key-operation-contract.json", - "contractSha256": "f7c76b5500654d6e05adf869c50d67d7d376e93119b5a93db933edd4c7d0c7b4", + "contractSha256": "0c28b642db338e44a8fac584f5502d77b18f591c31e5a28ea744afc850a114fd", "sourceInputs": { "algorithm": "c03-source-inputs-v1", "count": 431, - "sha256": "ef3f2b2d2fd8b8ec62e6231d8318a7bc9d69dfe6d6949588ca397373d8bb9308", + "sha256": "28e69f831778a7355fbcdbcb66a6af18467a15404861492fb4baa57ca64c5048", "anchors": [ "AbstractSchemaFactory.java", "AiAliasServiceChange.java", @@ -1638,41 +1669,151 @@ "removedTuples": [], "changedTuples": [], "historicalInventoryPreserved": true, - "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE" + "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE", + "previousReview": { + "status": "SOURCE_REEXTRACTED_NOT_SIGNED_NOT_RUNTIME_ACCEPTANCE", + "baseCommit": "48c052d569364e962938f1d2b6442330456113c3", + "contract": "scripts/api-key-operation-contract.json", + "contractSha256": "f7c76b5500654d6e05adf869c50d67d7d376e93119b5a93db933edd4c7d0c7b4", + "sourceInputs": { + "algorithm": "c03-source-inputs-v1", + "count": 431, + "sha256": "ef3f2b2d2fd8b8ec62e6231d8318a7bc9d69dfe6d6949588ca397373d8bb9308", + "anchors": [ + "AbstractSchemaFactory.java", + "AiAliasServiceChange.java", + "AiAuditQuery.java", + "AiAuditQueryResourceManager.java", + "AiAuditQueryRunner.java", + "AiAuditQueryService.java", + "AiAuditSourcePage.java", + "AiAuditSourcePageInput.java", + "AiCatalogLaunchAccess.java", + "AiCatalogLaunchAccessTest.java", + "AiCatalogLaunchBinding.java", + "AiCatalogLaunchBindingTest.java", + "AiCatalogLaunchDelegation.java", + "AiCatalogLaunchDelegationTest.java", + "AiCatalogLaunchGateway.java", + "AiCatalogLaunchGraph.java", + "AiCatalogLaunchHumanInputTest.java", + "AiCatalogLaunchInput.java", + "AiCatalogLaunchMaterializer.java", + "AiCatalogLaunchMaterializerTest.java", + "AiCatalogLaunchObservation.java", + "AiCatalogLaunchObservationTest.java", + "AiCatalogLaunchOptionsResourceManager.java", + "AiCatalogLaunchOptionsService.java", + "AiCatalogLaunchOptionsStore.java", + "AiCatalogLaunchOptionsStoreTest.java", + "AiCatalogLaunchOwnerContractTest.java", + "AiCatalogLaunchPlanResourceManager.java", + "AiCatalogLaunchPlanService.java", + "AiCatalogLaunchPlanStore.java", + "AiCatalogLaunchPlanStoreTest.java", + "AiCatalogLaunchPrivate.java", + "AiCatalogLaunchPrivateTest.java", + "AiCatalogLaunchProspectiveParentTest.java", + "AiCatalogLaunchResponseFilter.java", + "AiCatalogLaunchRunner.java", + "AiCatalogLaunchWorkflowTest.java", + "AiExternalServiceChange.java", + "AiResourceChangeDescriptor.java", + "AiResourceChangeDomain.java", + "AiResourceChangePlanService.java", + "AiResourceChangePlanStore.java", + "AiResourceChangeProcessObserver.java", + "AiResourceViewResourceManager.java", + "AiResourceViewService.java", + "ApiAuditPayload.java", + "ApiAuthenticator.java", + "ApiCookieAuthentication.java", + "ApiKeyAuditSink.java", + "ApiKeyAuthorizationService.java", + "ApiKeyOperations.java", + "ApiKeyProcessAuthorization.java", + "ApiKeyQueryScopes.java", + "ApiKeyTargetResolver.java", + "ApiServerConfig.java", + "AuditLogOutPutFilter.java", + "AuditServiceImpl.java", + "CatalogLaunchTransformAuthorization.java", + "DefaultAuthorizationProvider.java", + "DefaultObjectMetaDataManager.java", + "DefaultProcessInstanceImpl.java", + "DefaultProcessesParser.java", + "EngineContext.java", + "EventBasedProcessHandler.java", + "EventDiagnostics.java", + "ExternalDynamicExtensionHandler.java", + "FileSchemaFactory.java", + "GenericWhitelistedProxy.java", + "IaasApiConfig.java", + "NotWritablePostProcessor.java", + "ProcessAuthorization.java", + "ProcessEventDecorator.java", + "RequestReRouterHandler.java", + "ResourceManagerRequestHandler.java", + "SchemaFactoryImpl.java", + "ServiceConsumeMapDaoImpl.java", + "TokenAuthLookup.java", + "TransformInspect.java" + ], + "markers": [ + "@Type", + "@Actions", + " getLinkInternal(", + " getLink(", + " resourceActionInternal(", + " collectionActionInternal(", + " resourceAction(", + " collectionAction(" + ] + }, + "tupleCount": 1396, + "tupleSha256": "8f1c0fb378663b0b3d272efba2f211cb210eb6f17cb8035aefa33b56feeaf36a", + "addedTuples": [], + "removedTuples": [], + "changedTuples": [], + "historicalInventoryPreserved": true, + "producerCommit": "NOT_ASSIGNED_UNSIGNED_WORKTREE" + } } } } } - } + }, + "reviewManifest": "scripts/api-key-operation-review-0.183.366.json", + "reviewManifestSha256": "44e30398885ac2eee8af7aef8bcc576c2b3d90b347e30cef6ecc96469934f0c7" }, - "reviewManifest": "scripts/api-key-operation-review-0.183.366.json", - "reviewManifestSha256": "44e30398885ac2eee8af7aef8bcc576c2b3d90b347e30cef6ecc96469934f0c7" + "reviewManifest": "scripts/api-key-operation-review-0.183.367.json", + "reviewManifestSha256": "d4ecd4a95d4d541b5042f410aafcf64342c2d8900a899e93fedb7b640b395669", + "nativeActionOperandLockOrder": { + "owners": [ + "aiCatalogStackLaunchPlan", + "aiResourceChangePlan", + "aiServiceUpgradePlan" + ], + "rootCause": "Outer native API write transaction retained a prefetch row lock after application-lock release; concurrent receipt GET inverted lock order.", + "fix": "Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults.", + "lockTimeoutChanges": 0, + "retryChanges": 0, + "removedLocks": 0, + "databaseChanges": 0, + "uiPollingCausalityClaimed": false, + "runtimeAcceptanceClaimed": false + } }, - "reviewManifest": "scripts/api-key-operation-review-0.183.367.json", - "reviewManifestSha256": "d4ecd4a95d4d541b5042f410aafcf64342c2d8900a899e93fedb7b640b395669", - "nativeActionOperandLockOrder": { - "owners": [ - "aiCatalogStackLaunchPlan", - "aiResourceChangePlan", - "aiServiceUpgradePlan" - ], - "rootCause": "Outer native API write transaction retained a prefetch row lock after application-lock release; concurrent receipt GET inverted lock order.", - "fix": "Native Catalog-launch, resource-change and upgrade action operands use only exact framework request markers and authorized read snapshots. No prefetch observation writes or retained plan/credential row locks precede the action application lock. Ordinary GET observation and final action owner, role, Key revision, source/snapshot, CAS and one-claim checks remain; no timeout, retry, schema or grant expansion. New upgrade plans bind the complete strategy in their private target snapshot so older engines reject them before claim; genuine legacy image-only plans keep classic snapshots and original defaults.", - "lockTimeoutChanges": 0, - "retryChanges": 0, - "removedLocks": 0, - "databaseChanges": 0, - "uiPollingCausalityClaimed": false, - "runtimeAcceptanceClaimed": false - } + "reviewManifest": "scripts/api-key-operation-review-0.183.368.json", + "reviewManifestSha256": "71c1ab8fe2080bfda7a52c9a8349754c0c67b48a9eb16b6181a42ce8a4528d15" }, - "reviewManifest": "scripts/api-key-operation-review-0.183.368.json", - "reviewManifestSha256": "71c1ab8fe2080bfda7a52c9a8349754c0c67b48a9eb16b6181a42ce8a4528d15" + "reviewManifest": "scripts/api-key-operation-review-0.183.369.json", + "reviewManifestSha256": "8adb6a02225e5806561146b83a34461eb6d640fddf5a0a8aa552d0ccf81d53c9" }, - "reviewManifest": "scripts/api-key-operation-review-0.183.369.json", - "reviewManifestSha256": "8adb6a02225e5806561146b83a34461eb6d640fddf5a0a8aa552d0ccf81d53c9" + "reviewManifest": "scripts/api-key-operation-review-0.183.370.json", + "reviewManifestSha256": "0d2859a40044d1f1085748b9be65192e9df4d20e2967a02eee1b2f967b8fa4ea" }, - "reviewManifest": "scripts/api-key-operation-review-0.183.370.json", - "reviewManifestSha256": "0d2859a40044d1f1085748b9be65192e9df4d20e2967a02eee1b2f967b8fa4ea" + "reviewManifest": "scripts/api-key-operation-review-0.183.371.json", + "reviewManifestSha256": "ea469a6e29b2c528f2bc6854b0387fc16efa6de4ffc322a431628b214ca68eaa" } } diff --git a/scripts/api-key-operation-review-0.183.371.json b/scripts/api-key-operation-review-0.183.371.json new file mode 100644 index 0000000000..a35c1bb07e --- /dev/null +++ b/scripts/api-key-operation-review-0.183.371.json @@ -0,0 +1,56 @@ +{ + "schemaVersion": 1, + "version": "0.183.371", + "scope": "SOURCE_ONLY_NOT_RUNTIME_ACCEPTANCE", + "reviewedBaseCommit": "7f48482e17bec0eab1010fb045e1771b2b695caf", + "sourceInputs": { + "algorithm": "c03-source-inputs-v1", + "baseline": { + "count": 458, + "sha256": "7082897c9cfe9d80534ac02a871e0ef4e79356f1c23a618aadd8616be89c3069" + }, + "reviewed": { + "count": 458, + "sha256": "59ec478abbedf97403a9615c80785b9f69fc289b703650b8316c03730c1244f7" + }, + "addedAnchors": [] + }, + "tupleReview": { + "baseline": 1396, + "candidate": 1396, + "added": 0, + "removed": 0, + "semanticChanges": 0, + "dispositionChanges": 0, + "sha256": "8f1c0fb378663b0b3d272efba2f211cb210eb6f17cb8035aefa33b56feeaf36a" + }, + "delta": [ + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchAccess.java", + "before": "5f0e1de8995318226e0c8ae48acbd3a6e07b025e7fa068daa1bc5945e25c6d49", + "after": "18c9a38db699f6f0413a2c6601971ec7b7f753e1e8c26f54331df53562ecc782" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/ai/catalog/AiCatalogLaunchDelegation.java", + "before": "e75f911701471e63f85b70df645040a4142704f8da94f3008ed2187c43b35a1d", + "after": "27f232a3190210e1a34c158bd379c71d6356e39fbca58a069b2fc0378f91bdd2" + }, + { + "path": "code/iaas/auth-logic/src/main/java/io/cattle/platform/iaas/api/auth/change/AiResourceChangeDomain.java", + "before": "2378a8875ae2a2b76e70d7c57a0af715d02f6165228bc2063139f208298669d7", + "after": "e95bbe9a62c4f7b19be4d72973669794d219654f8787b931456bf09fe38193b7" + } + ], + "authorizationBoundary": "Host metadata checks validate the native Host model independently of driver kind. Event-local Catalog delegation supports only exact GET v1/v2-beta version-root metadata and normal schema reads after proof/event/owner/source/frozenStack validation; metadata is repeatable while original writes retain claims/replay boundaries. Safe rejection audit does not attribute forged proofs to a plan or Key. All native role/Key, schema, lock, human approval and business-resource permission rules remain unchanged.", + "frozenV1SchemaFilesUnchanged": true, + "nativeRoleGrantsUnchanged": true, + "serializedOrPublicSchemaChanges": 0, + "extractor": { + "constructionSha256": "078ffe48881469fe5eb3cb43a15fa41b480fe70e11bd5f63102ded6b35a6d076", + "parserSha256": "7cc8e70d9f3ea391eb74fef2490f4407aa47833c585b6729c62b3d9e5829ac2b", + "extractorSha256": "112d2ff723d88acbe7bb45ff48c9c9018feb93b08031a3e86f867030871477fc" + }, + "historicalProducerAndReviewChainsPreserved": true, + "artifactQualificationClaimed": false, + "runtimeAcceptanceClaimed": false +} diff --git a/scripts/build b/scripts/build index 613906e566..3d508324b4 100755 --- a/scripts/build +++ b/scripts/build @@ -16,7 +16,7 @@ fi SOURCE_REVISION=${SOURCE_REVISION:-$(git rev-parse HEAD)} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)} -ENGINE_VERSION=${ENGINE_VERSION:-0.183.370} +ENGINE_VERSION=${ENGINE_VERSION:-0.183.371} case "$SOURCE_REVISION" in ''|*[!0-9a-f]*) diff --git a/scripts/check-cattle-jdk25-standalone b/scripts/check-cattle-jdk25-standalone index edd03ff2f2..bacbd69f4a 100755 --- a/scripts/check-cattle-jdk25-standalone +++ b/scripts/check-cattle-jdk25-standalone @@ -6,7 +6,7 @@ cd "$repo_root" java_cmd=${JAVA_CMD:-java} runner_image=${RC16_CATTLE_STANDALONE_JDK_IMAGE:-maven:3.9.16-eclipse-temurin-25@sha256:1b1fc6d0168ea616afd1c861d6f32ec37c9ec2ffe88a0351b3771dd4ad86b0d8} -expected_version=${EXPECTED_ENGINE_VERSION:-${ENGINE_VERSION:-0.183.370}} +expected_version=${EXPECTED_ENGINE_VERSION:-${ENGINE_VERSION:-0.183.371}} artifact=${RC16_CATTLE_STANDALONE_JAR:-dist/artifacts/cattle.jar} # Full-package invokes Maven directly, without scripts/build's final copy. diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index a5d07eb28c..25597f3c9a 100755 --- a/scripts/check-pasturestack-source +++ b/scripts/check-pasturestack-source @@ -51,7 +51,7 @@ fi project_version=$(sed -n 's/^[[:space:]]*\([^<]*\)<\/version>[[:space:]]*$/\1/p' code/meta-parent/pom.xml | head -n 1) [[ "$project_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail non_numeric_project_version -require_line code/meta-parent/pom.xml ' 0.183.370' +require_line code/meta-parent/pom.xml ' 0.183.371' require_line code/meta-parent/pom.xml ' 33.7.2-jre' require_line ubuntu-security-packages.lock 'libbrotli1=1.2.0-3ubuntu0.1' require_line ubuntu-security-packages.lock 'apt=3.2.1ubuntu0.1' @@ -95,7 +95,7 @@ require_line Dockerfile.dapper 'ARG MAVEN_VERSION=3.9.16' require_line ubuntu-apt.lock "UBUNTU_APT_SNAPSHOT='20260826T000000Z'" require_line ubuntu-apt.lock "UBUNTU_APT_SECURITY_SNAPSHOT='20261002T000000Z'" require_line ubuntu-apt.lock "UBUNTU_APT_OPENSSL_VERSION='3.5.5-1ubuntu3.7'" -require_line ubuntu-apt.lock "UBUNTU_APT_CURL_BOOTSTRAP_VERSION='8.18.0-1ubuntu2.4'" +require_line ubuntu-apt.lock "UBUNTU_APT_CURL_BOOTSTRAP_VERSION='8.18.0-1ubuntu2.8'" require_line ubuntu-apt.lock "UBUNTU_APT_CURL_VERSION='8.18.0-1ubuntu2.8'" # Final builder readback cannot silently retain the snapshot bootstrap curl. require_line Dockerfile 'ADD --checksum=sha256:2c418a61d7165d0cde01d9cb05ae025cc41fc75ef6d3f095996bd045ba0945dc https://security.ubuntu.com/ubuntu/pool/main/c/curl/curl_8.18.0-1ubuntu2.8_amd64.deb /tmp/curl.deb' @@ -124,7 +124,7 @@ require_line Dockerfile.dapper ' test "$(dpkg-query -W -f='"'"'${Version} require_line Dockerfile.dapper ' java -version 2>&1 | grep -F '"'"'25.0.5'"'"' && \' require_line Dockerfile.dapper ' test "$(javac -version 2>&1)" = '"'"'javac 25.0.5'"'"' && \' require_text Dockerfile.dapper 'curl="${UBUNTU_APT_CURL_BOOTSTRAP_VERSION}"' -require_line ubuntu-apt.lock "UBUNTU_APT_PYTHON3_14_VERSION='3.14.4-1ubuntu0.1'" +require_line ubuntu-apt.lock "UBUNTU_APT_PYTHON3_14_VERSION='3.14.4-1ubuntu0.2'" require_line Dockerfile ' rm -f /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; \' require_line Dockerfile.dapper ' rm -f /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; \' require_line Dockerfile 'ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb /tmp/ca-certificates.deb' @@ -137,7 +137,7 @@ require_text Dockerfile 'Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.g require_text Dockerfile.dapper 'Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' require_text Dockerfile 'Acquire::https::CaInfo "/etc/ssl/certs/ca-certificates.crt";\nAcquire::https::Verify-Peer "true";\nAcquire::https::Verify-Host "true";' require_text Dockerfile.dapper 'Acquire::https::CaInfo "/etc/ssl/certs/ca-certificates.crt";\nAcquire::https::Verify-Peer "true";\nAcquire::https::Verify-Host "true";' -require_line Dockerfile ' apt-get upgrade -y; \' +require_line Dockerfile ' install-snapshot-packages upgrade -y; \' require_line Dockerfile.dapper 'COPY --chmod=0755 scripts/install-snapshot-packages /usr/local/bin/install-snapshot-packages' require_line Dockerfile.dapper ' install-snapshot-packages upgrade -y; \' # Package selection remains bound to the signed frozen snapshot. The alternate @@ -151,7 +151,7 @@ require_text scripts/install-snapshot-packages 'SHA256:' require_text scripts/install-snapshot-packages '--no-download' python3 -B scripts/test-install-snapshot-packages.py python3 -B scripts/test-maven-distribution-download.py -python3 -B scripts/test-apt-security-bootstrap.py +python3 -B scripts/test-apt-security-bootstrap.py -v python3 -B scripts/test-apt-fixed-vex.py require_line Makefile 'DAPPER_SOURCE ?= /usr/src/pasturestack/orchestration-engine' require_line Dockerfile.dapper 'ENV DAPPER_SOURCE=/usr/src/pasturestack/orchestration-engine' @@ -495,4 +495,4 @@ require_line README.md 'PastureStack is an independent community effort to prese require_line README.md 'Rancher 1.6 ecosystem. It is not affiliated with Rancher Labs or SUSE.' require_line ORIGIN.md '- Preserved upstream boundary: `82d154a53f4089fecfb9f320caad826bb4f6055f`' -printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.370 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.5 go=1.27.3 curl=8.18.0-1ubuntu2.8 openssl=3.5.5-1ubuntu3.7 maven=3.9.16 patched_hazelcast=5.7.6 docker_cli=29.9.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' +printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.371 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.5 go=1.27.3 curl=8.18.0-1ubuntu2.8 openssl=3.5.5-1ubuntu3.7 maven=3.9.16 patched_hazelcast=5.7.6 docker_cli=29.9.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' diff --git a/scripts/check-release-artifact b/scripts/check-release-artifact index 42e838281c..811b7a342f 100755 --- a/scripts/check-release-artifact +++ b/scripts/check-release-artifact @@ -4,7 +4,7 @@ set -euo pipefail cd "$(dirname "$0")/.." artifact=${1:-dist/artifacts/cattle.jar} -expected_version=${EXPECTED_ENGINE_VERSION:-0.183.370} +expected_version=${EXPECTED_ENGINE_VERSION:-0.183.371} test -f "$artifact" artifact=$(realpath "$artifact") diff --git a/scripts/install-snapshot-packages b/scripts/install-snapshot-packages index 2b35bf672d..220f5799d4 100644 --- a/scripts/install-snapshot-packages +++ b/scripts/install-snapshot-packages @@ -11,15 +11,29 @@ fail() } case "${1:-}" in install|upgrade) ;; *) fail unsupported_operation ;; esac +local_root=/tmp +local_args= for argument do case "$argument" in - install|upgrade|-y|--yes|--no-install-recommends) ;; + install|upgrade|-y|--yes|--no-install-recommends|--only-upgrade|--no-remove) ;; + "$local_root/"*) + [ "$1" = install ] || fail unsupported_argument + leaf=${argument#"$local_root/"} + printf '%s\n' "$leaf" | grep -Eq '^[a-zA-Z0-9][a-zA-Z0-9.+~_-]*\.deb$' || fail invalid_local_path + [ -d "$local_root" ] && [ ! -L "$local_root" ] && [ -f "$argument" ] && [ ! -L "$argument" ] || fail invalid_local_file + case " $local_args " in *" $argument "*) fail duplicate_local_argument ;; esac + local_args="$local_args $argument" + ;; ''|-*|*[!a-zA-Z0-9.+:~=_-]*) fail unsupported_argument ;; esac done . /licenses/ubuntu-apt.lock -for snapshot in "$UBUNTU_APT_SNAPSHOT" "$UBUNTU_APT_SECURITY_SNAPSHOT"; do +security_lock=/licenses/ubuntu-security-packages.lock +[ -f "$security_lock" ] && [ ! -L "$security_lock" ] || fail invalid_security_lock +[ "$(grep -c '^# Official Ubuntu 26.04 security/updates snapshot:' "$security_lock")" = 1 ] || fail invalid_security_snapshot_header +micro_snapshot=$(sed -n 's|^# Official Ubuntu 26.04 security/updates snapshot: \([0-9]\{8\}T[0-9]\{6\}Z\)$|\1|p' "$security_lock") +for snapshot in "$UBUNTU_APT_SNAPSHOT" "$UBUNTU_APT_SECURITY_SNAPSHOT" "$micro_snapshot"; do printf '%s\n' "$snapshot" | grep -Eq '^[0-9]{8}T[0-9]{6}Z$' || fail invalid_snapshot_lock done archives=/var/cache/apt/archives @@ -30,7 +44,10 @@ cleanup() [ -z "$cache_copy" ] || rm -f -- "$cache_copy" rm -f -- "$work/payload/package.deb" "$work/payload/package.deb.FAILED" [ ! -d "$work/payload" ] || rmdir -- "$work/payload" - rm -f -- "$work/plan.raw" "$work/plan.tsv" "$work/names" + rm -f -- "$work/plan-archives/lock" + [ ! -d "$work/plan-archives/partial" ] || rmdir -- "$work/plan-archives/partial" + [ ! -d "$work/plan-archives" ] || rmdir -- "$work/plan-archives" + rm -f -- "$work/plan.raw" "$work/plan.tsv" "$work/names" "$work/local.tsv" "$work/local-notes" "$work/local-cache.tsv" rmdir -- "$work" } trap cleanup 0 @@ -40,12 +57,47 @@ mkdir -m 0700 "$work/payload" chown _apt "$work/payload" mkdir -p "$archives" +same_local() +{ + [ -f "$1" ] && [ ! -L "$1" ] || return 1 + [ "$(wc -c < "$1" | tr -d '[:space:]')" = "$2" ] || return 1 + local_actual=$(sha256sum "$1") + [ "${local_actual%% *}" = "$3" ] +} + +# The recipe authenticates its local inputs with checksum-pinned ADD and native +# metadata checks. This utility only snapshots and preserves those exact bytes; +# it does not own another package catalogue or establish new authenticity. +: > "$work/local.tsv" +: > "$work/local-notes" +for local_file in $local_args; do + local_size=$(wc -c < "$local_file" | tr -d '[:space:]') + local_hash=$(sha256sum "$local_file"); local_hash=${local_hash%% *} + local_package=$(dpkg-deb -f "$local_file" Package) + local_version=$(dpkg-deb -f "$local_file" Version) + local_architecture=$(dpkg-deb -f "$local_file" Architecture) + local_filename=${local_package}_${local_version}_${local_architecture}.deb + printf '%s\n' "$local_filename" | grep -Eq '^[a-zA-Z0-9][a-zA-Z0-9.+~:_-]*\.deb$' || fail invalid_local_metadata + printf '%s\t%s\t%s\t%s\n' "$local_file" "$local_size" "$local_hash" "$local_filename" >> "$work/local.tsv" + printf "Note, selecting '%s' instead of '%s'\n" "$local_package" "$local_file" >> "$work/local-notes" + printf "Note, selecting '%s:%s' instead of '%s'\n" "$local_package" "$local_architecture" "$local_file" >> "$work/local-notes" + same_local "$local_file" "$local_size" "$local_hash" || fail local_input_changed +done + # These options do not add or refresh any index. The final command keeps the # caller's exact operation/package arguments, but cannot download another plan. -apt-get -o Acquire::ForceHash=SHA256 -o "Dir::Cache::archives=$archives" \ +plan_archives=$archives +if [ -n "$local_args" ]; then + # Native APT cache hits check only size and omit their URI. An empty + # planning cache exposes every selected local filename for SHA binding. + plan_archives=$work/plan-archives + mkdir -m 0700 "$plan_archives" +fi +apt-get -o Acquire::ForceHash=SHA256 -o "Dir::Cache::archives=$plan_archives" \ --print-uris --download-only -y "$@" > "$work/plan.raw" : > "$work/plan.tsv" : > "$work/names" +: > "$work/local-cache.tsv" decode_leaf() { @@ -53,9 +105,43 @@ decode_leaf() printf '%s' "$1" | sed -e 's/%3[aA]/:/g' -e 's/%2[bB]/+/g' -e 's/%7[eE]/~/g' } +parse_local() +{ + [ "$#" -eq 3 ] || [ "$#" -eq 4 ] || fail malformed_local_plan + local_uri=${1#\'}; local_uri=${local_uri%\'} + case "$local_uri" in + file:///*) local_file=${local_uri#file://} ;; + file:/*) local_file=${local_uri#file:} ;; + *) fail invalid_local_uri ;; + esac + local_cache_filename=$2 + printf '%s\n' "$local_cache_filename" | grep -Eq '^[a-zA-Z0-9][a-zA-Z0-9.+~%:_-]*\.deb$' || fail invalid_filename + local_plan_filename=$(decode_leaf "$local_cache_filename") + local_plan_size=$3 + local_plan_hash= + if [ "$#" -eq 4 ]; then + case "$4" in SHA256:*) local_plan_hash=${4#SHA256:} ;; *) fail invalid_local_plan_hash ;; esac + printf '%s\n' "$local_plan_hash" | grep -Eq '^[0-9a-fA-F]{64}$' || fail invalid_local_plan_hash + local_plan_hash=$(printf '%s' "$local_plan_hash" | tr A-F a-f) + fi + tab=$(printf '\t') + local_record=$(grep -F -- "$local_file$tab" "$work/local.tsv") || fail unbound_local_plan + # All manifest fields are single safe words; globbing remains disabled. + set -- $local_record + [ "$#" -eq 4 ] && [ "$1" = "$local_file" ] || fail unbound_local_plan + [ "$local_plan_size" = "$2" ] || fail local_plan_size_changed + [ -z "$local_plan_hash" ] || [ "$local_plan_hash" = "$3" ] || fail local_plan_hash_changed + [ "$local_plan_filename" = "${local_file##*/}" ] || [ "$local_plan_filename" = "$4" ] || fail local_plan_filename_changed + same_local "$1" "$2" "$3" || fail local_input_changed + if grep -Fx -- "$local_plan_filename" "$work/names" >/dev/null; then fail duplicate_filename; fi + printf '%s\n' "$local_plan_filename" >> "$work/names" + printf '%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$local_cache_filename" >> "$work/local-cache.tsv" +} + parse_plan() { while IFS= read -r line || [ -n "$line" ]; do + if [ -n "$local_args" ] && grep -Fx -- "$line" "$work/local-notes" >/dev/null; then continue; fi case "$line" in \'*) ;; *://*|*SHA256:*|*.deb*) fail malformed_plan ;; @@ -63,6 +149,7 @@ parse_plan() esac # Globbing is disabled; never eval APT's quoted URI or any plan text. set -- $line + case "${1:-}" in \'file:*\') parse_local "$@"; continue ;; esac [ "$#" -eq 4 ] || fail malformed_plan case "$1" in \'*\') uri=${1#\'}; uri=${uri%\'} ;; *) fail malformed_plan ;; esac filename=$2 @@ -77,7 +164,7 @@ parse_plan() *) fail invalid_snapshot_uri ;; esac date=${relative%%/*} - case "$date" in "$UBUNTU_APT_SNAPSHOT"|"$UBUNTU_APT_SECURITY_SNAPSHOT") ;; *) fail unlocked_snapshot ;; esac + case "$date" in "$UBUNTU_APT_SNAPSHOT"|"$UBUNTU_APT_SECURITY_SNAPSHOT"|"$micro_snapshot") ;; *) fail unlocked_snapshot ;; esac pool=${relative#*/} printf '%s\n' "$pool" | grep -Eq '^pool/(main|universe)/[a-z0-9][a-z0-9+.-]*/[a-z0-9][a-z0-9+.-]*/[a-zA-Z0-9][a-zA-Z0-9.+~%:_-]*\.deb$' || fail invalid_pool_path leaf=$(decode_leaf "${pool##*/}") @@ -95,6 +182,15 @@ parse_plan() } parse_plan +# Retain the complete validated remote closure before any transport can fail. +# Raw APT text and local file records are deliberately not diagnostics. +printf 'SNAPSHOT_PACKAGE_PLAN_BEGIN\n' +tab=$(printf '\t') +while IFS="$tab" read -r plan_uri plan_pool plan_filename plan_size plan_hash; do + printf 'SNAPSHOT_PACKAGE_PLAN pool=%s size=%s sha256=%s\n' "$plan_pool" "$plan_size" "$plan_hash" +done < "$work/plan.tsv" +printf 'SNAPSHOT_PACKAGE_PLAN_END\n' + verified() { [ -f "$1" ] || return 1 @@ -148,4 +244,33 @@ while IFS="$tab" read -r uri pool filename size hash; do fi done < "$work/plan.tsv" +tab=$(printf '\t') +while IFS="$tab" read -r local_file local_size local_hash local_filename; do + same_local "$local_file" "$local_size" "$local_hash" || fail local_input_changed + destination=$archives/$local_filename + [ ! -L "$destination" ] && [ ! -d "$destination" ] || fail invalid_local_cache_destination + if [ -e "$destination" ]; then + same_local "$destination" "$local_size" "$local_hash" || fail local_cache_verification_failed + continue + fi + cache_copy=$(mktemp "$archives/.snapshot-package.XXXXXX") + cp -- "$local_file" "$cache_copy" + same_local "$cache_copy" "$local_size" "$local_hash" || fail local_cache_copy_changed + chmod 0644 "$cache_copy" + mv -f -- "$cache_copy" "$destination" + cache_copy= +done < "$work/local-cache.tsv" + +tab=$(printf '\t') +while IFS="$tab" read -r local_file local_size local_hash local_filename; do + same_local "$local_file" "$local_size" "$local_hash" || fail local_input_changed +done < "$work/local.tsv" + +# APT's size-only cache hit must never substitute unverified same-sized bytes. +tab=$(printf '\t') +while IFS="$tab" read -r local_file local_size local_hash local_filename; do + same_local "$local_file" "$local_size" "$local_hash" || fail local_input_changed + same_local "$archives/$local_filename" "$local_size" "$local_hash" || fail local_cache_verification_failed +done < "$work/local-cache.tsv" + apt-get -o "Dir::Cache::archives=$archives" --no-download "$@" diff --git a/scripts/install-ubuntu-security-updates.sh b/scripts/install-ubuntu-security-updates.sh index a8630ddca3..e4ee7d2a60 100644 --- a/scripts/install-ubuntu-security-updates.sh +++ b/scripts/install-ubuntu-security-updates.sh @@ -28,17 +28,30 @@ done < "$lock" if test "$#" -gt 0; then mkdir -p "$tmp/lists/partial" printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/20261023T000000Z\nSuites: resolute-updates resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' > "$tmp/security.sources" + # One native configuration is shared by signed-index update, the exact URI + # planner and the final no-download solver. No global APT source is changed. + for path in "$tmp" "$trusted_ca"; do + case "$path" in /*) ;; *) exit 1;; esac + case "$path" in *[!a-zA-Z0-9_./+~-]*) exit 1;; esac + done + (umask 077; printf '%s\n' \ + "Dir::Etc::sourcelist \"$tmp/security.sources\";" \ + "Dir::State::lists \"$tmp/lists\";" \ + "Dir::Cache::pkgcache \"$tmp/pkgcache.bin\";" \ + "Dir::Cache::srcpkgcache \"$tmp/srcpkgcache.bin\";" \ + 'Dir::Etc::sourceparts "-";' \ + "Acquire::https::CaInfo \"$trusted_ca\";" \ + 'Acquire::https::Verify-Peer "true";' \ + 'Acquire::https::Verify-Host "true";' \ + 'Acquire::AllowInsecureRepositories "false";' \ + 'APT::Get::AllowUnauthenticated "false";' \ + 'Acquire::Retries "5";' > "$tmp/security.conf") apt_security() { - apt-get -o Dir::Etc::sourcelist="$tmp/security.sources" \ - -o Dir::State::lists="$tmp/lists" \ - -o Dir::Cache::pkgcache="$tmp/pkgcache.bin" -o Dir::Cache::srcpkgcache="$tmp/srcpkgcache.bin" \ - -o Dir::Etc::sourceparts=- -o Acquire::https::CaInfo="$trusted_ca" \ - -o Acquire::https::Verify-Peer=true -o Acquire::https::Verify-Host=true \ - -o Acquire::AllowInsecureRepositories=false -o APT::Get::AllowUnauthenticated=false \ - -o Acquire::Retries=5 "$@" + APT_CONFIG="$tmp/security.conf" apt-get "$@" } apt_security update - DEBIAN_FRONTEND=noninteractive apt_security install -y --only-upgrade --no-install-recommends --no-remove "$@" + DEBIAN_FRONTEND=noninteractive APT_CONFIG="$tmp/security.conf" \ + install-snapshot-packages install -y --only-upgrade --no-install-recommends --no-remove "$@" fi while IFS= read -r entry || test -n "$entry"; do case "$entry" in ''|'#'*) continue;; esac diff --git a/scripts/test-apt-security-bootstrap.py b/scripts/test-apt-security-bootstrap.py index 021b250789..e9db256108 100644 --- a/scripts/test-apt-security-bootstrap.py +++ b/scripts/test-apt-security-bootstrap.py @@ -2,6 +2,7 @@ """Exercise the exact APT bootstrap security boundary without network or installation.""" import os from pathlib import Path +import re import shutil import subprocess import tempfile @@ -36,6 +37,173 @@ def block(name): class AptSecurityBootstrapTest(unittest.TestCase): + def test_existing_openssl_security_overlay_precedes_base_upgrade_without_changing_its_scope(self): + lock = (REPO / "ubuntu-apt.lock").read_text(encoding="utf-8") + version = re.search(r"^UBUNTU_APT_OPENSSL_VERSION='([^']+)'$", lock, re.M).group(1) + security = (REPO / "ubuntu-security-packages.lock").read_text(encoding="utf-8") + for package in ("libssl3t64", "openssl", "openssl-provider-legacy"): + self.assertIn(package + "=" + version + "\n", security) + for name in ("Dockerfile", "Dockerfile.dapper"): + with self.subTest(builder=name): + source = (REPO / name).read_text(encoding="utf-8") + tool = "install-snapshot-packages" + install = tool + " install -y --no-install-recommends \\\n libssl3t64=\"${UBUNTU_APT_OPENSSL_VERSION}\"" + upgrade = "install-snapshot-packages" + " upgrade -y;" + def verify(text): + at = text.index(install) + self.assertLess(at, text.index(upgrade)) + # Upgrade sees only the original base indexes. The same + # existing security source is restored afterwards. + remove = text.index(" rm -f /etc/apt/sources.list.d/pasturestack-security.sources;", at) + self.assertLess(remove, text.index(upgrade)) + self.assertIn(" apt-get update;", text[remove:text.index(upgrade)]) + self.assertEqual(2, text.count('"${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources;')) + for package in ("libssl3t64", "openssl", "openssl-provider-legacy"): + self.assertIn(package + '=\"${UBUNTU_APT_OPENSSL_VERSION}\"', text[at:remove]) + self.assertIn("dpkg-query -W", text[at:remove]) + self.assertIn("dpkg-query -W", text[text.index(upgrade):]) + self.assertNotIn("--allow-downgrades", text) + self.assertNotIn("--force", text[at:remove]) + verify(source) + # The original failure order must remain a failing contract. + before = source[:source.index(upgrade)] + after = source[source.index(upgrade):] + with self.assertRaises(AssertionError): + verify(before.replace(install, "old-late-install", 1) + after + install) + + def test_existing_final_package_family_bootstraps_the_original_base_with_native_apt(self): + packages = ("libpython3.14-minimal libpython3.14-stdlib python3.14-minimal " + "python3.14 python3.14-venv curl libcurl3t64-gnutls libcurl4t64 " + "ca-certificates libbrotli1 libpcre2-8-0 libpcre2-posix3") + lock = (REPO / "ubuntu-apt.lock").read_text(encoding="utf-8") + for field, version in {"PYTHON3_14": "3.14.4-1ubuntu0.2", + "CURL_BOOTSTRAP": "8.18.0-1ubuntu2.8", + "CURL": "8.18.0-1ubuntu2.8", + "CA_CERTIFICATES": "20260816ubuntu0.26.04.1"}.items(): + self.assertIn("UBUNTU_APT_" + field + "_VERSION='" + version + "'", lock) + gate = (REPO / "scripts/check-pasturestack-source").read_text(encoding="utf-8") + for field, version in {"PYTHON3_14": "3.14.4-1ubuntu0.2", + "CURL_BOOTSTRAP": "8.18.0-1ubuntu2.8"}.items(): + self.assertIn('require_line ubuntu-apt.lock "UBUNTU_APT_' + field + "_VERSION='" + version + "'\"", gate) + for name in ("Dockerfile", "Dockerfile.dapper"): + with self.subTest(builder=name): + source = (REPO / name).read_text(encoding="utf-8") + upgrade = "install-snapshot-packages" + " upgrade -y;" + local_start = " set --; \\\n for package in " + packages + "; do" + start = source.index(local_start) + stop = source.index(upgrade) + def verify(text): + self.assertIn(local_start, text) + begin = text.index(local_start) + end = text.index(upgrade) + self.assertLess(begin, end) + phase = text[begin:end] + self.assertEqual(2, phase.count("for package in " + packages + "; do")) + for package in packages.split(): + self.assertRegex(text, r"ADD --checksum=sha256:[0-9a-f]{64} https://(?:security|archive)\.ubuntu\.com/[^\n]+ /tmp/" + re.escape(package) + r"\.deb\n") + install = phase.index('install-snapshot-packages install -y --no-install-recommends "$@";') + self.assertIn('COPY --chmod=0755 scripts/install-snapshot-packages /usr/local/bin/install-snapshot-packages', text) + helper = (REPO / "scripts/install-snapshot-packages").read_text(encoding="utf-8") + self.assertIn('apt-get -o "Dir::Cache::archives=$archives" --no-download "$@"', helper) + for field in ("Package", "Version", "Architecture"): + self.assertLess(phase.index('.deb" ' + field), install) + self.assertIn("dpkg-query -W", phase[install:]) + self.assertIn("architecture=all", phase) + self.assertIn("version=1.2.0-3ubuntu0.1", phase) + self.assertIn("version=10.46-1ubuntu0.1", phase) + reset = text.rindex("rm -f /etc/apt/sources.list.d/pasturestack-security.sources;", 0, begin) + self.assertIn("apt-get update;", text[reset:begin]) + self.assertNotIn("--force", phase) + self.assertNotIn("--allow-downgrades", text) + verify(source) + with self.assertRaises(AssertionError): + verify(source[:start] + source[stop:] + source[start:stop]) + with self.assertRaises(AssertionError): + verify(source.replace("python3.14 python3.14-venv curl", "python3.14 curl", 1)) + + def test_only_three_missing_final_security_dependencies_join_the_original_early_phase(self): + packages = "libexpat1 libsqlite3-0 libssh2-1t64" + marker = " set --; \\\n for package in " + packages + "; do" + lock = (REPO / "ubuntu-security-packages.lock").read_text(encoding="utf-8") + versions = dict(line.split("=", 1) for line in lock.splitlines() + if line and not line.startswith("#")) + for name in ("Dockerfile", "Dockerfile.dapper"): + source = (REPO / name).read_text(encoding="utf-8") + tool = "install-snapshot-packages" + + def verify(text): + self.assertIn(marker, text) + start = text.index(marker) + end = text.index(" rm -f /etc/apt/sources.list.d/pasturestack-security.sources;", start) + phase = text[start:end] + self.assertIn('test "$(grep -c "^${package}=" /licenses/ubuntu-security-packages.lock)" -eq 1;', phase) + self.assertIn('entry=$(grep "^${package}=" /licenses/ubuntu-security-packages.lock);', phase) + self.assertIn('set -- "$@" "$entry";', phase) + self.assertEqual(1, phase.count(tool + " install -y --no-install-recommends")) + self.assertIn('openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}" "$@";', phase) + self.assertIn('for entry in "$@"; do', phase) + self.assertIn('"${entry%%=*}")" = "${entry#*=}";', phase) + self.assertLess(end, text.index("install-snapshot-packages" + " upgrade -y;")) + return phase + + phase = verify(source) + # Neither all-overlay promotion nor a later install is equivalent. + with self.assertRaises(AssertionError): + verify(source.replace("for package in " + packages + "; do", + "for package in " + packages + " libc6; do", 1)) + with self.assertRaises(AssertionError): + verify(source.replace(marker, " set --; \\\n for package in $(cat /licenses/ubuntu-security-packages.lock); do", 1)) + for mode in ("ok", "missing", "duplicate", "wrong-readback"): + with self.subTest(builder=name, mode=mode), tempfile.TemporaryDirectory(prefix="engine-final-deps-") as directory: + root = Path(directory) + calls = root / "calls" + fixture_lock = root / "security.lock" + value = lock + if mode == "missing": + value = value.replace("libsqlite3-0=" + versions["libsqlite3-0"] + "\n", "") + if mode == "duplicate": + value += "libsqlite3-0=" + versions["libsqlite3-0"] + "\n" + fixture_lock.write_text(value, encoding="utf-8", newline="\n") + for command in ("apt-get", "install-snapshot-packages"): + path = root / command + path.write_text('#!/bin/sh\nset -eu\nprintf "%s\\n" "$@" > "$PST_FINAL_CALLS"\n', encoding="utf-8", newline="\n") + path.chmod(0o755) + path = root / "dpkg-query" + path.write_text('''#!/bin/sh +set -eu +[ "$#" = 3 ] && [ "$1" = -W ] || exit 91 +if [ "$PST_FINAL_MODE" = wrong-readback ] && [ "$3" = libsqlite3-0 ]; then + printf '3.46.1-9ubuntu0.2\n' +else + entry=$(grep "^$3=" "$PST_FINAL_LOCK") + printf '%s\n' "${entry#*=}" +fi +''', encoding="utf-8", newline="\n") + path.chmod(0o755) + shell_path = root.as_posix() + system_bin = Path(shell()).resolve().parent.as_posix() + if os.name == "nt": + shell_path = "/" + root.drive[0].lower() + shell_path[2:] + system_bin = "/" + system_bin[0].lower() + system_bin[2:] + env = dict(os.environ, PATH=str(root) + os.pathsep + os.environ["PATH"], + PST_FINAL_MODE=mode, PST_FINAL_CALLS=calls.as_posix(), + PST_FINAL_LOCK=fixture_lock.as_posix(), PST_FINAL_BIN=shell_path, + PST_FINAL_SYSTEM_BIN=system_bin) + script = 'set -eu\nPATH="$PST_FINAL_BIN:$PST_FINAL_SYSTEM_BIN:$PATH"\nexport PATH\nUBUNTU_APT_OPENSSL_VERSION=' + versions["openssl"] + "\n" + script += phase.replace("/licenses/ubuntu-security-packages.lock", '"' + fixture_lock.as_posix() + '"') + result = subprocess.run([shell(), "-c", script], env=env, capture_output=True, timeout=15) + self.assertEqual(b"", result.stderr) + if mode == "ok": + self.assertEqual(0, result.returncode) + arguments = calls.read_text().splitlines() + expected = {p + "=" + versions[p] for p in packages.split()} + expected.update(p + "=" + versions[p] for p in ("libssl3t64", "openssl", "openssl-provider-legacy")) + self.assertEqual(expected, set(arguments[3:])) + self.assertEqual(6, len(arguments[3:])) + else: + self.assertNotEqual(0, result.returncode) + self.assertEqual(mode == "wrong-readback", calls.exists()) + def run_block(self, name, mode): _, production = block(name) with tempfile.TemporaryDirectory(prefix="engine-apt-security-") as directory: @@ -118,5 +286,107 @@ def test_install_or_installed_version_failure_never_calls_apt(self): self.assertEqual(["-i /tmp/libapt-pkg7.0.deb /tmp/apt.deb"], calls) + + def test_final_security_overlay_shares_private_native_config_and_rejects_package_set_changes(self): + source = (REPO / 'scripts/install-ubuntu-security-updates.sh').read_text() + def posix(path): + value = Path(path).as_posix() + return '/' + value[0].lower() + value[2:] if os.name == 'nt' and value[1:3] == ':/' else value + def execute(mode, unsafe_ca=False): + with tempfile.TemporaryDirectory(prefix='final-security-transport-') as directory: + home = Path(directory).resolve() + binary = home / 'bin' + binary.mkdir() + work = home / 'work' + work.mkdir() + release = home / 'os-release' + release.write_text('ID=ubuntu\nVERSION_ID=26.04\n', newline='\n') + lock = home / 'security.lock' + lock.write_text('existing-package=2.0\nabsent-package=2.0\n', newline='\n') + ca = home / ('unsafe ca.crt' if unsafe_ca else 'ca.crt') + ca.write_text('fixture CA bytes\n', newline='\n') + def executable(name, text): + path = binary / name + path.write_text(text, encoding='utf-8', newline='\n') + path.chmod(0o755) + if os.name == 'nt': + # Model Linux mktemp's absolute POSIX path; Git's native + # Windows utility otherwise returns a drive-letter path. + executable('mktemp', '''#!/bin/sh +set -eu +[ "$#" = 1 ] && [ "$1" = -d ] && [ "$TMPDIR" = "$TEST_HOME/work" ] || exit 93 +mkdir "$TMPDIR/private" +printf '%s\\n' "$TMPDIR/private" +''') + executable('dpkg-query', '''#!/bin/sh +set -eu +if [ "$#" = 2 ]; then + status=installed + if [ -f "$TEST_HOME/after" ] && [ "$TEST_MODE" = removed ]; then status=removed; fi + printf 'existing-package\\t%s\\n' "$status" + if [ -f "$TEST_HOME/after" ] && [ "$TEST_MODE" = added ]; then printf 'unexpected-package\\tinstalled\\n'; fi +elif [ "$3" = absent-package ]; then + exit 1 +else + case "$2" in + *db:Status-Status*) printf 'installed\\n' ;; + *) if [ -f "$TEST_HOME/after" ]; then printf '2.0\\n'; else printf '1.0\\n'; fi ;; + esac +fi +''') + executable('dpkg', '''#!/bin/sh +set -eu +[ "$#" = 4 ] && [ "$1" = --compare-versions ] && [ "$3" = ge ] && [ "$4" = 2.0 ] || exit 90 +[ "$2" = 2.0 ] +''') + executable('apt-get', '''#!/bin/sh +set -eu +[ "$#" = 1 ] && [ "$1" = update ] || exit 91 +[ -s "$APT_CONFIG" ] +printf '%s\\n' update > "$TEST_HOME/update" +cp "$APT_CONFIG" "$TEST_HOME/observed-config" +cp "${APT_CONFIG%/*}/security.sources" "$TEST_HOME/observed-sources" +''') + executable('install-snapshot-packages', '''#!/bin/sh +set -eu +[ "$DEBIAN_FRONTEND" = noninteractive ] +cmp "$APT_CONFIG" "$TEST_HOME/observed-config" +printf '%s\\n' "$@" > "$TEST_HOME/argv" +[ "$#" = 6 ] && [ "$1" = install ] && [ "$2" = -y ] && [ "$3" = --only-upgrade ] && [ "$4" = --no-install-recommends ] && [ "$5" = --no-remove ] && [ "$6" = existing-package=2.0 ] || exit 92 +[ "$TEST_MODE" != transport-failure ] || exit 47 +: > "$TEST_HOME/after" +''') + script = home / 'security-updates.sh' + self.assertEqual(1, source.count('. /etc/os-release')) + script.write_text(source.replace('. /etc/os-release', '. ' + posix(release)), encoding='utf-8', newline='\n') + env = dict(os.environ, PATH=':'.join([posix(binary), posix(Path(shell()).resolve().parent), '/usr/bin', '/bin']), + TMPDIR=posix(work), TEST_HOME=posix(home), TEST_MODE=mode) + result = subprocess.run([shell(), str(script), posix(lock), posix(ca)], env=env, + capture_output=True, text=True, timeout=15) + self.assertEqual([], list(work.iterdir()), 'Every outcome cleans its own private APT directory') + if unsafe_ca: + self.assertFalse((home / 'update').exists()) + self.assertFalse((home / 'argv').exists()) + else: + self.assertTrue((home / 'argv').exists(), result.stdout + result.stderr) + self.assertEqual(['install', '-y', '--only-upgrade', '--no-install-recommends', '--no-remove', + 'existing-package=2.0'], (home / 'argv').read_text().splitlines()) + config = (home / 'observed-config').read_text() + self.assertIn('Dir::Etc::sourceparts "-";', config) + self.assertIn('Acquire::https::CaInfo "' + posix(ca) + '";', config) + for key in ('Acquire::https::Verify-Peer', 'Acquire::https::Verify-Host'): + self.assertIn(key + ' "true";', config) + for key in ('Acquire::AllowInsecureRepositories', 'APT::Get::AllowUnauthenticated'): + self.assertIn(key + ' "false";', config) + self.assertEqual(11, len(config.splitlines())) + self.assertIn('https://snapshot.ubuntu.com/ubuntu/20261023T000000Z', (home / 'observed-sources').read_text()) + self.assertIn('Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg', (home / 'observed-sources').read_text()) + return result + self.assertEqual(0, execute('ok').returncode) + for mode in ('transport-failure', 'added', 'removed'): + with self.subTest(mode=mode): + self.assertNotEqual(0, execute(mode).returncode) + self.assertNotEqual(0, execute('ok', unsafe_ca=True).returncode) + if __name__ == "__main__": unittest.main() diff --git a/scripts/test-install-snapshot-packages.py b/scripts/test-install-snapshot-packages.py index 51427d5fa7..bd6babfa4a 100644 --- a/scripts/test-install-snapshot-packages.py +++ b/scripts/test-install-snapshot-packages.py @@ -44,6 +44,11 @@ def setUp(self): self.home = Path(self.tmp.name) self.bin = self.home / 'bin' self.bin.mkdir() + self.local_root = self.home / 'local-inputs' + self.local_root.mkdir() + self.security_lock = self.home / 'security.lock' + self.security_lock.write_bytes((ROOT / 'ubuntu-security-packages.lock').read_bytes()) + self.config_log = self.home / 'config.log' self.cache = self.home / 'archives' self.plan = self.home / 'plan' self.write_plan(self.uri_line()) @@ -60,13 +65,28 @@ def setUp(self): self.args = ['install', '-y', '--no-install-recommends', 'unzip=6.0-29ubuntu1'] self.make_script('apt-get', r'''#!/bin/sh set -eu +printf '%s\n' "${APT_CONFIG-}" >> "$TEST_CONFIG_LOG" printf 'BEGIN\n' >> "$TEST_APT_LOG" printf '%s\n' "$@" >> "$TEST_APT_LOG" case " $* " in - *' --print-uris '*) cat "$TEST_PLAN"; exit "$TEST_PLAN_STATUS" ;; + *' --print-uris '*) + cat "$TEST_PLAN" + if [ -n "$TEST_CORRUPT_LOCAL" ]; then printf 'changed input\n' > "$TEST_CORRUPT_LOCAL"; fi + exit "$TEST_PLAN_STATUS" ;; *' --no-download '*) : > "$TEST_INSTALLED" ;; *) exit 91 ;; esac +''') + self.make_script('dpkg-deb', r'''#!/bin/sh +set -eu +[ "$#" = 3 ] && [ "$1" = -f ] || exit 96 +name=${2##*/}; name=${name%.deb} +case "$3" in + Package) printf '%s\n' "$name" ;; + Version) printf '1.0\n' ;; + Architecture) if [ "$name" = ca-certificates ]; then printf 'all\n'; else printf 'amd64\n'; fi ;; + *) exit 97 ;; +esac ''') self.make_script('chown', r'''#!/bin/sh set -eu @@ -126,7 +146,10 @@ def setUp(self): # has no configurable apt-helper, cache or lock-file escape hatch. script = SOURCE for old, new in ( + ('local_root=/tmp', 'local_root=' + shlex.quote(self.shell_path(self.local_root))), ('. /licenses/ubuntu-apt.lock', '. ' + shlex.quote((ROOT / 'ubuntu-apt.lock').as_posix())), + ('security_lock=/licenses/ubuntu-security-packages.lock', + 'security_lock=' + shlex.quote(self.shell_path(self.security_lock))), ('archives=/var/cache/apt/archives', 'archives=' + shlex.quote(self.cache.as_posix())), ('/usr/lib/apt/apt-helper', shlex.quote(helper.as_posix()))): self.assertEqual(1, script.count(old)) @@ -154,19 +177,23 @@ def shell_path(path): return '/' + value[0].lower() + value[2:] return value - def run_helper(self, *, archive='ok', snapshot='ok', plan_status=0, args=None): + def run_helper(self, *, apt_config=None, archive='ok', snapshot='ok', plan_status=0, args=None, corrupt_local=None): for path in (self.apt_log, self.helper_log, self.sleep_log, - self.snapshot_count, self.installed, self.chown_log): + self.snapshot_count, self.installed, self.chown_log, self.config_log): path.unlink(missing_ok=True) paths = [self.shell_path(self.bin), self.shell_path(Path(SH).resolve().parent)] paths.extend(['/usr/bin', '/bin'] if os.name == 'nt' else [os.environ['PATH']]) env = dict(os.environ, PATH=':'.join(paths), TEST_APT_LOG=self.apt_log.as_posix(), TEST_HELPER_LOG=self.helper_log.as_posix(), TEST_PLAN=self.plan.as_posix(), TEST_INSTALLED=self.installed.as_posix(), + TEST_CONFIG_LOG=self.config_log.as_posix(), TEST_CHOWN_LOG=self.chown_log.as_posix(), TEST_SLEEP_LOG=self.sleep_log.as_posix(), TEST_SNAPSHOT_COUNT=self.snapshot_count.as_posix(), TEST_GOOD_PAYLOAD=self.good.as_posix(), TEST_BAD_PAYLOAD=self.bad.as_posix(), - TEST_ARCHIVE_MODE=archive, TEST_SNAPSHOT_MODE=snapshot, TEST_PLAN_STATUS=str(plan_status)) + TEST_ARCHIVE_MODE=archive, TEST_SNAPSHOT_MODE=snapshot, TEST_PLAN_STATUS=str(plan_status), + TEST_CORRUPT_LOCAL='' if corrupt_local is None else self.shell_path(corrupt_local)) + if apt_config is not None: + env['APT_CONFIG'] = self.shell_path(apt_config) return subprocess.run([SH, self.script.as_posix(), *(args or self.args)], env=env, capture_output=True, text=True, timeout=15) @@ -200,12 +227,262 @@ def test_posix_syntax_and_fixed_production_transport(self): docker = (ROOT / 'Dockerfile.dapper').read_text() self.assertIn('ENV LANG=C.UTF-8 LC_ALL=C.UTF-8', docker) self.assertIn('COPY --chmod=0755 scripts/install-snapshot-packages /usr/local/bin/install-snapshot-packages', docker) - self.assertEqual(3, docker.count(' install-snapshot-packages ')) - self.assertEqual(2, docker.count(' apt-get update;')) + self.assertEqual(4, docker.count(' install-snapshot-packages ')) + self.assertEqual(3, docker.count(' apt-get update;')) + # The first/final security indexes bracket the original base-only plan. + updates = [m.start() for m in re.finditer(' apt-get update;', docker)] + security = '"${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources;' + self.assertEqual(2, docker.count(security)) + phases = [docker.index(security), updates[0], + docker.index(' rm -f /etc/apt/sources.list.d/pasturestack-security.sources;'), + updates[1], docker.index(' install-snapshot-packages upgrade -y;'), + docker.rindex(security), updates[2]] + self.assertEqual(sorted(phases), phases) self.assertIn('Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg', docker) self.assertIn('Acquire::https::Verify-Peer "true"', docker) self.assertIn('Acquire::https::Verify-Host "true"', docker) + def test_explicit_local_inputs_keep_the_signed_remote_closure_and_original_no_download_install(self): + # The utility owns transport, while the recipe owns its exact twelve-package list. + names = ['bootstrap-a', 'bootstrap-b'] + inputs = [] + plan = '' + for i, name in enumerate(names): + file = self.local_root / (name + '.deb'); file.write_bytes(GOOD) + path = self.shell_path(file); inputs.append(path) + arch = 'all' if name == 'ca-certificates' else 'amd64' + selection = name if i % 2 == 0 else name + ':' + arch + plan += f"Note, selecting '{selection}' instead of '{path}'\n" + uri = 'file:' + ('//' if i % 2 else '') + path + filename = name + '.deb' if i % 2 else f'{name}_1.0_{arch}.deb' + # APT permits local volatile-source records without any hash. + plan += f"'{uri}' {filename} {len(GOOD)}" + (f' SHA256:{HASH}' if i % 2 else '') + '\n' + pools = ( + 'pool/main/k/krb5/libgssapi-krb5-2_1.22.1-2ubuntu4.1_amd64.deb', + 'pool/main/r/readline/readline-common_8.3-4_all.deb', + 'pool/universe/p/python-pip/python3-pip-whl_25.1.1%2bdfsg-1ubuntu2_all.deb', + 'pool/universe/s/setuptools/python3-setuptools-whl_78.1.1-0.1build1_all.deb', + ) + for pool in pools: + plan += self.uri_line(uri=SNAPSHOT + pool, filename=pool.rsplit('/', 1)[1]) + self.write_plan(plan) + args = ['install', '-y', '--no-install-recommends', *inputs] + result = self.run_helper(args=args) + self.assertEqual(0, result.returncode, result.stderr) + self.assertEqual([f'https://archive.ubuntu.com/ubuntu/{p}|SHA256:{HASH}' for p in pools], self.downloads()) + self.assertEqual([], self.sleeps()) + self.assertEqual(args, self.apt_calls()[0][-len(args):]) + self.assertEqual(args, self.apt_calls()[1][-len(args):]) + self.assertIn('--no-download', self.apt_calls()[1]) + for name in names: + self.assertEqual(GOOD, (self.local_root / (name + '.deb')).read_bytes()) + + def test_complete_remote_diagnostics_precede_transport_without_raw_or_local_data(self): + file = self.local_root / 'never-publish-local.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + pool2 = 'pool/main/r/remaining/remaining_2.0_amd64.deb' + remote = self.uri_line() + self.uri_line(uri=SNAPSHOT + pool2, filename=pool2.rsplit('/', 1)[1]) + plan = f"'file:{path}' never-publish-local.deb {len(GOOD)}\n" + remote + self.write_plan(plan + 'private-note-must-not-appear\n') + helper = self.bin / 'apt-helper-mock' + text = helper.read_text() + needle = "printf '%s|%s\\n'" + self.assertEqual(1, text.count(needle)) + helper.write_text(text.replace(needle, "printf 'TRANSPORT_STARTED\\n'\n" + needle), encoding='utf-8', newline='\n') + result = self.run_helper(args=['install', '-y', path], archive='fail', snapshot='fail') + self.assert_rejected(result) + expected = [f'SNAPSHOT_PACKAGE_PLAN pool={pool} size={len(GOOD)} sha256={HASH}' + for pool in (POOL, pool2)] + self.assertEqual(expected, [line for line in result.stdout.splitlines() + if line.startswith('SNAPSHOT_PACKAGE_PLAN pool=')]) + self.assertLess(result.stdout.index('SNAPSHOT_PACKAGE_PLAN_END'), result.stdout.index('TRANSPORT_STARTED')) + self.assertIn('package_download_failed', result.stderr) + for private in (path, 'file:', 'private-note-must-not-appear', 'Note, selecting'): + self.assertNotIn(private, result.stdout) + # A bad later row must prevent even the earlier valid row being logged. + for bad in (self.uri_line(digest='bad'), self.uri_line(uri='https://invalid.example/private')): + self.write_plan(self.uri_line() + bad) + result = self.run_helper(archive='fail', snapshot='fail') + self.assert_rejected(result) + self.assertEqual([], self.downloads()) + self.assertNotIn('SNAPSHOT_PACKAGE_PLAN', result.stdout) + self.assertNotIn('private', result.stdout) + + def model_native_local_cache_hit(self, filename): + # APT 3.2.1 omits an already cached URI after checking size, not SHA. + target = self.bin / 'apt-get' + text = target.read_text() + needle = ' cat "$TEST_PLAN"' + self.assertEqual(1, text.count(needle)) + replacement = ''' planning_cache= + for option do + case "$option" in Dir::Cache::archives=*) planning_cache=${option#*=} ;; esac + done + [ -n "$planning_cache" ] || exit 98 + if [ -f "$planning_cache/''' + filename + '''" ] && + [ "$(wc -c < "$planning_cache/''' + filename + '''" | tr -d '[:space:]')" = ''' + str(len(GOOD)) + ''' ]; then + exit "$TEST_PLAN_STATUS" + fi +''' + needle + target.write_text(text.replace(needle, replacement), encoding='utf-8', newline='\n') + + def test_local_inputs_use_empty_planning_cache_and_seed_exact_native_cache_names(self): + file = self.local_root / 'approved.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + filename = 'approved_1.0_amd64.deb' + self.model_native_local_cache_hit(filename) + self.write_plan(f"'file:{path}' {filename} {len(GOOD)}\n") + args = ['install', '-y', path] + # Empty real cache seeds bytes; a second call must still bind the URI + # through the separate empty planner, despite a native size-only hit. + for reused in (False, True): + result = self.run_helper(args=args) + self.assertEqual(0, result.returncode, result.stderr) + self.assertEqual(GOOD, (self.cache / filename).read_bytes()) + self.assertEqual(GOOD, file.read_bytes()) + self.assertEqual([], self.downloads()) + planning = next(v for v in self.apt_calls()[0] if v.startswith('Dir::Cache::archives=')) + self.assertTrue(planning.endswith('/plan-archives'), (reused, planning)) + self.assertNotEqual('Dir::Cache::archives=' + self.cache.as_posix(), planning) + self.assertEqual(args, self.apt_calls()[1][-len(args):]) + self.assertIn('--no-download', self.apt_calls()[1]) + + def test_local_cache_wrong_hash_size_or_directory_cannot_reach_native_install(self): + file = self.local_root / 'approved.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + filename = 'approved_1.0_amd64.deb' + self.model_native_local_cache_hit(filename) + self.write_plan(f"'file:{path}' {filename} {len(GOOD)}\n") + self.cache.mkdir() + target = self.cache / filename + self.assertEqual(len(GOOD), len(BAD)) + for mode, value in (('same-size-wrong-sha', BAD), ('wrong-size', GOOD + b'x'), ('directory', None)): + with self.subTest(mode=mode): + if value is None: + target.mkdir() + else: + target.write_bytes(value) + result = self.run_helper(args=['install', '-y', path]) + self.assert_rejected(result) + expected = 'invalid_local_cache_destination' if value is None else 'local_cache_verification_failed' + self.assertIn(expected, result.stderr) + self.assertEqual([], self.downloads()) + self.assertEqual(GOOD, file.read_bytes()) + if value is None: + target.rmdir() + else: + self.assertEqual(value, target.read_bytes()) + target.unlink() + + def test_local_and_remote_canonical_cache_collisions_reject_before_transport(self): + file = self.local_root / 'approved.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + filename = 'approved_1.0_amd64.deb' + local = f"'file:{path}' {filename} {len(GOOD)}\n" + remote = self.uri_line(uri=SNAPSHOT + 'pool/main/a/approved/' + filename, + filename=filename, digest=hashlib.sha256(BAD).hexdigest()) + for plan in (local + local, local + remote, remote + local): + with self.subTest(order=plan.startswith("'file:")): + self.write_plan(plan) + result = self.run_helper(args=['install', '-y', path]) + self.assert_rejected(result) + self.assertIn('duplicate_filename', result.stderr) + self.assertEqual([], self.downloads()) + self.assertNotIn('SNAPSHOT_PACKAGE_PLAN', result.stdout) + + def test_local_cache_last_sha_recheck_detects_tampering_after_atomic_seed(self): + file = self.local_root / 'approved.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + filename = 'approved_1.0_amd64.deb' + self.write_plan(f"'file:{path}' {filename} {len(GOOD)}\n") + real_mv = (Path(SH).resolve().parent / 'mv.exe').as_posix() if os.name == 'nt' else shutil.which('mv') + self.assertTrue(real_mv) + target = (self.cache / filename).as_posix() + self.make_script('mv', '#!/bin/sh\nset -eu\n' + shlex.quote(real_mv) + ' "$@"\n' + 'last=\nfor last do :; done\n[ "$last" = ' + shlex.quote(target) + ' ] || exit 98\n' + "printf 'evil package\\n' > \"$last\"\n") + result = self.run_helper(args=['install', '-y', path]) + self.assert_rejected(result) + self.assertIn('local_cache_verification_failed', result.stderr) + self.assertEqual(GOOD, file.read_bytes()) + self.assertEqual(BAD, (self.cache / filename).read_bytes()) + + def test_local_cache_symlink_is_rejected_even_when_target_bytes_match(self): + file = self.local_root / 'approved.deb' + file.write_bytes(GOOD) + path = self.shell_path(file) + filename = 'approved_1.0_amd64.deb' + self.write_plan(f"'file:{path}' {filename} {len(GOOD)}\n") + self.cache.mkdir() + try: + (self.cache / filename).symlink_to(file) + except OSError as error: + if os.name == 'nt' and getattr(error, 'winerror', None) == 1314: + self.skipTest('Windows symlink privilege unavailable; Linux CI must execute this control') + raise + result = self.run_helper(args=['install', '-y', path]) + self.assert_rejected(result) + self.assertIn('invalid_local_cache_destination', result.stderr) + self.assertEqual(GOOD, file.read_bytes()) + + def test_local_plan_rows_are_bound_to_explicit_argument_bytes_size_and_filename(self): + file = self.local_root / 'approved.deb'; file.write_bytes(GOOD) + other = self.local_root / 'unbound.deb'; other.write_bytes(GOOD) + path = self.shell_path(file) + args = ['install', '-y', path] + valid = f"'file:{path}' approved.deb {len(GOOD)} SHA256:{HASH}\n" + invalid = ( + valid.replace(path, self.shell_path(other)), + valid.replace(path, '/etc/passwd'), + valid.replace('file:', 'file://foreign.invalid'), + valid.replace('approved.deb ' + str(len(GOOD)), 'other.deb ' + str(len(GOOD))), + valid.replace(str(len(GOOD)) + ' SHA256:', str(len(GOOD) + 1) + ' SHA256:'), + valid.replace(HASH, 'f' * 64), valid.replace('SHA256:', 'MD5Sum:'), + valid.replace(HASH, ''), + f"Note, selecting 'unbound' instead of '{path}'\n", + self.uri_line().replace('SHA256:', 'MD5Sum:'), + ) + for row in invalid: + with self.subTest(plan=row): + self.write_plan(self.uri_line() + row) + self.assert_rejected(self.run_helper(args=args)) + self.assertEqual([], self.downloads()) + self.write_plan(valid) + self.assert_rejected(self.run_helper()) # A file row never grants a new local argument. + + def test_local_regular_paths_and_final_snapshot_are_required_before_install(self): + file = self.local_root / 'approved.deb'; file.write_bytes(GOOD) + outside = self.home / 'outside.deb'; outside.write_bytes(GOOD) + directory = self.local_root / 'directory.deb'; directory.mkdir() + for path in (self.shell_path(outside), self.shell_path(directory), + self.shell_path(self.local_root / 'missing.deb'), + self.shell_path(file.parent) + '/../approved.deb', self.shell_path(file) + '?query'): + with self.subTest(path=path): + self.assert_rejected(self.run_helper(args=['install', '-y', path])) + self.assertEqual([], self.downloads()) + # No file rows are required if APT omits them; final input identity still is. + result = self.run_helper(args=['install', '-y', self.shell_path(file)], corrupt_local=file) + self.assert_rejected(result) + self.assertIn('local_input_changed', result.stderr) + self.assertEqual(1, len(self.apt_calls())) + + def test_local_symlink_never_reaches_the_native_planner(self): + file = self.local_root / 'approved.deb'; file.write_bytes(GOOD) + link = self.local_root / 'link.deb' + try: + link.symlink_to(file) + except OSError as error: + if os.name == 'nt': self.skipTest('Native symlink permission is unavailable on this Windows fixture host') + raise error + self.assert_rejected(self.run_helper(args=['install', '-y', self.shell_path(link)])) + self.assertEqual([], self.downloads()) + self.assertFalse(self.apt_log.exists()) + def test_official_archive_first_and_exact_no_download_args(self): self.write_plan('Reading package lists...\n unzip\n' + self.uri_line()) result = self.run_helper() @@ -378,5 +655,57 @@ def test_upgrade_and_empty_plan_keep_original_operation(self): self.assertEqual(['upgrade', '-y'], self.apt_calls()[1][-2:]) + + def test_security_overlay_uses_canonical_snapshot_flags_and_same_private_config(self): + header = self.security_lock.read_text().splitlines()[0] + date = re.fullmatch(r'# Official Ubuntu 26.04 security/updates snapshot: (\d{8}T\d{6}Z)', header).group(1) + self.write_plan(self.uri_line('https://snapshot.ubuntu.com/ubuntu/' + date + '/' + POOL)) + config = self.home / 'private-apt.conf' + config.write_text('Acquire::https::Verify-Peer "true";\n') + args = ['install', '-y', '--only-upgrade', '--no-install-recommends', '--no-remove', 'unzip=6.0-29ubuntu1'] + result = self.run_helper(args=args, apt_config=config) + self.assertEqual(0, result.returncode, result.stderr) + self.assertTrue(self.installed.exists()) + self.assertEqual([self.shell_path(config)] * 2, self.config_log.read_text().splitlines()) + calls = self.apt_log.read_text().split('BEGIN\n')[1:] + self.assertEqual(2, len(calls)) + self.assertEqual(args, calls[-1].splitlines()[3:]) + self.assertEqual('--no-download', calls[-1].splitlines()[2]) + self.assertEqual('https://archive.ubuntu.com/ubuntu/' + POOL, + self.helper_log.read_text().split('|')[0]) + for option in ('--allow-downgrades', '--allow-unauthenticated', '--force-yes'): + with self.subTest(option=option): + result = self.run_helper(args=args + [option], apt_config=config) + self.assertNotEqual(0, result.returncode) + self.assertFalse(self.installed.exists()) + self.assertFalse(self.apt_log.exists()) + + def test_security_snapshot_header_is_unique_closed_and_cannot_escape_the_canonical_lock(self): + original = self.security_lock.read_bytes() + for value in (b'', b'# Official Ubuntu 26.04 security/updates snapshot: nope\n', + original.splitlines()[0] + b'\n' + original, + b'# Official Ubuntu 26.04 security/updates snapshot: 20261023T000000Z;evil\n'): + with self.subTest(header=value[:80]): + self.security_lock.write_bytes(value) + result = self.run_helper() + self.assertNotEqual(0, result.returncode) + self.assertFalse(self.apt_log.exists()) + self.assertFalse(self.installed.exists()) + self.security_lock.write_bytes(original) + self.write_plan(self.uri_line('https://snapshot.ubuntu.com/ubuntu/20261024T000000Z/' + POOL)) + result = self.run_helper() + self.assertNotEqual(0, result.returncode) + self.assertFalse(self.installed.exists()) + self.assertFalse(self.helper_log.exists()) + if os.name != 'nt': + target = self.home / 'other.lock' + target.write_bytes(original) + self.security_lock.unlink() + self.security_lock.symlink_to(target) + result = self.run_helper() + self.assertNotEqual(0, result.returncode) + self.assertFalse(self.apt_log.exists()) + self.assertFalse(self.installed.exists()) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/ubuntu-apt.lock b/ubuntu-apt.lock index dcdd4a1f38..a0137a3bc8 100644 --- a/ubuntu-apt.lock +++ b/ubuntu-apt.lock @@ -1,15 +1,15 @@ # Ubuntu 26.04 package lock for reproducible PastureStack builds. -# Refresh the base snapshot and its exact direct-package versions together. -# Security overlays are added after the base install and pin only listed fixes. +# Refresh the base snapshot and its unoverlaid direct-package versions together. +# Existing final fixed packages bootstrap the base install; later overlays retain their exact pins. UBUNTU_APT_SNAPSHOT='20260826T000000Z' UBUNTU_APT_SECURITY_SNAPSHOT='20261002T000000Z' UBUNTU_APT_OPENSSL_VERSION='3.5.5-1ubuntu3.7' UBUNTU_APT_BASH_VERSION='5.3-2ubuntu1' -UBUNTU_APT_CA_CERTIFICATES_VERSION='20260601~26.04.1' -# The base snapshot bootstrap is replaced by the SHA256-pinned security packages. -UBUNTU_APT_CURL_BOOTSTRAP_VERSION='8.18.0-1ubuntu2.4' +UBUNTU_APT_CA_CERTIFICATES_VERSION='20260816ubuntu0.26.04.1' +# Bootstrap uses the same SHA256-pinned final Curl packages. +UBUNTU_APT_CURL_BOOTSTRAP_VERSION='8.18.0-1ubuntu2.8' UBUNTU_APT_CURL_VERSION='8.18.0-1ubuntu2.8' UBUNTU_APT_GIT_VERSION='1:2.53.0-1ubuntu1' UBUNTU_APT_GZIP_VERSION='1.14-1~exp2ubuntu1.1' @@ -20,7 +20,7 @@ UBUNTU_APT_MARIADB_CLIENT_VERSION='1:11.8.6-5ubuntu0.1' UBUNTU_APT_POSTGRESQL_CLIENT_VERSION='18+290ubuntu1' UBUNTU_APT_PROCPS_VERSION='2:4.0.4-9ubuntu1' UBUNTU_APT_PYTHON3_VERSION='3.14.3-0ubuntu2' -UBUNTU_APT_PYTHON3_14_VERSION='3.14.4-1ubuntu0.1' +UBUNTU_APT_PYTHON3_14_VERSION='3.14.4-1ubuntu0.2' UBUNTU_APT_PYTHON3_PIP_VERSION='25.1.1+dfsg-1ubuntu2' UBUNTU_APT_PYTHON3_VENV_VERSION='3.14.3-0ubuntu2' UBUNTU_APT_TAR_VERSION='1.35+dfsg-4ubuntu0.4'