From c68e137e32d3afb32281e0ed5ef1ae65ee36bfeb Mon Sep 17 00:00:00 2001 From: Artem Korolev Date: Thu, 6 Aug 2026 00:41:48 +0000 Subject: [PATCH 1/3] fix(action): preserve bounded Dagger Shell transport Write bounded scripts to owner-only files instead of interpolating quoted shell source through dagger-for-github. Materialize env files, runtime directories, and Docker sockets as typed host objects so runner-temporary paths remain stable. --- action.yml | 3 +- github-action/prepare-workflow.sh | 9 ++++- github-action/rush-delivery-local | 59 ++++++++++++++++++++++++++-- test/github-action.test.ts | 41 +++++++++++++------ test/local-source-launcher.test.ts | 63 +++++++++++++++++++++++++++++- 5 files changed, 154 insertions(+), 21 deletions(-) diff --git a/action.yml b/action.yml index d4ea952..74df339 100644 --- a/action.yml +++ b/action.yml @@ -266,10 +266,9 @@ runs: DAGGER_NO_NAG: ${{ inputs.dagger-no-nag }} with: version: ${{ inputs.dagger-version }} - verb: call + verb: ${{ steps.prepare.outputs.verb }} module: ${{ steps.prepare.outputs.module }} args: ${{ steps.prepare.outputs.args }} - shell: ${{ steps.prepare.outputs.shell }} workdir: ${{ inputs.workdir }} dagger-flags: ${{ inputs.dagger-flags }} cloud-token: ${{ inputs.dagger-cloud-token }} diff --git a/github-action/prepare-workflow.sh b/github-action/prepare-workflow.sh index 9f9f728..cc3b7e6 100755 --- a/github-action/prepare-workflow.sh +++ b/github-action/prepare-workflow.sh @@ -352,6 +352,7 @@ releasePackages | release-packages) esac dagger_shell="" +dagger_verb="call" if [[ ${bounded_local_copy} == "true" ]]; then bounded_repo="${repo_input:-${GITHUB_WORKSPACE:-.}}" launcher_args=( @@ -366,7 +367,11 @@ if [[ ${bounded_local_copy} == "true" ]]; then fi launcher_args+=("--" "${args[@]}") dagger_shell="$("${GITHUB_ACTION_PATH}/github-action/rush-delivery-local" "${launcher_args[@]}")" - call_args="" + dagger_shell_file="${action_temp}/dagger-shell" + printf '%s\n' "${dagger_shell}" >"${dagger_shell_file}" + chmod 0600 "${dagger_shell_file}" + dagger_verb="shell" + call_args="${dagger_shell_file}" else call_args="$(shell_quote_args "${args[@]}")" if [[ -n ${INPUT_EXTRA_ARGS-} ]]; then @@ -375,8 +380,8 @@ else fi write_output module "${module}" +write_output verb "${dagger_verb}" write_output args "${call_args}" -write_output shell "${dagger_shell}" write_output workflow-env-file "${workflow_env_file}" write_output deploy-env-file "${deploy_env_file}" write_output release-env-file "${release_env_file}" diff --git a/github-action/rush-delivery-local b/github-action/rush-delivery-local index 4d8e6fa..18baeb4 100755 --- a/github-action/rush-delivery-local +++ b/github-action/rush-delivery-local @@ -193,6 +193,21 @@ rush_delivery_reject_adapter_arguments() { done } +rush_delivery_host_object_kind() { + case "$1" in + --workflow-env-file | --deploy-env-file | --release-env-file) + printf 'file' + ;; + --runtime-files) + printf 'directory' + ;; + --docker-socket) + printf 'unix-socket' + ;; + *) ;; + esac +} + rush_delivery_build_shell() { local repo="$1" local entrypoint="$2" @@ -201,17 +216,53 @@ rush_delivery_build_shell() { local script local pattern local argument + local argument_name + local argument_value + local host_object_kind + local host_object_index=0 + local host_object_name + local invocation="" + local -a function_arguments=("$@") + local index script="repo=\$(host | directory $(rush_delivery_shell_literal "${repo}")" for pattern in "${RUSH_DELIVERY_EXCLUDES[@]}"; do script+=" --exclude=$(rush_delivery_shell_literal "${pattern}")" done script+=")" - script+=$'\n' - script+="local-source --repo=\$repo | ${entrypoint}" - for argument in "$@"; do - script+=" $(rush_delivery_shell_literal "${argument}")" + + for ((index = 0; index < ${#function_arguments[@]}; index += 1)); do + argument="${function_arguments[index]}" + argument_name="${argument%%=*}" + argument_value="" + + host_object_kind="$(rush_delivery_host_object_kind "${argument_name}")" + if [[ -n ${host_object_kind} ]]; then + if [[ ${argument} == *=* ]]; then + argument_value="${argument#*=}" + elif ((index + 1 < ${#function_arguments[@]})); then + index=$((index + 1)) + argument_value="${function_arguments[index]}" + else + rush_delivery_local_die "${argument_name} requires a value" + fi + + if [[ -n ${argument_value} ]]; then + host_object_name="rush_delivery_input_${host_object_index}" + host_object_index=$((host_object_index + 1)) + script+=$'\n' + script+="${host_object_name}=\$(host | ${host_object_kind} $(rush_delivery_shell_literal "${argument_value}"))" + invocation+=" ${argument_name}=\$${host_object_name}" + else + invocation+=" $(rush_delivery_shell_literal "${argument_name}=")" + fi + else + invocation+=" $(rush_delivery_shell_literal "${argument}")" + fi done + + script+=$'\n' + script+="local-source --repo=\$repo | ${entrypoint}${invocation}" if [[ -n ${trusted_extra_args} ]]; then script+=" ${trusted_extra_args}" fi diff --git a/test/github-action.test.ts b/test/github-action.test.ts index ead02d9..91d1b7b 100644 --- a/test/github-action.test.ts +++ b/test/github-action.test.ts @@ -58,7 +58,10 @@ test("action metadata defines a composite action over dagger-for-github", () => ) as { inputs: Record; runs: { - steps: Array<{ uses?: string }>; + steps: Array<{ + uses?: string; + with?: Record; + }>; using: string; }; }; @@ -86,6 +89,12 @@ test("action metadata defines a composite action over dagger-for-github", () => "dagger/dagger-for-github@27b130bf0f79a7f6fbbbe0fbca6760dc9bb40a77", ), ); + const daggerStep = metadata.runs.steps.find( + (step) => step.uses !== undefined, + ); + assert.equal(daggerStep?.with?.verb, "${{ steps.prepare.outputs.verb }}"); + assert.equal(daggerStep?.with?.args, "${{ steps.prepare.outputs.args }}"); + assert.equal(daggerStep?.with?.shell, undefined); }); test("prepare workflow emits bounded local-copy Dagger Shell through the shared launcher", async () => { @@ -116,15 +125,25 @@ test("prepare workflow emits bounded local-copy Dagger Shell through the shared assert.equal(result.status, 0, result.stderr); const outputs = parseGithubOutput(await readFile(outputPath, "utf8")); - assert.equal(outputs.args, ""); - assert.match(outputs.shell, /^repo=\$\(host \| directory /u); - assert.match(outputs.shell, /--exclude='\*\*\/node_modules'/u); - assert.match(outputs.shell, /--exclude='\*\*\/generated'/u); - assert.match(outputs.shell, /--exclude='!apps\/api\/generated'/u); - assert.match(outputs.shell, /local-source --repo=\$repo \| workflow/u); - assert.match(outputs.shell, /--host-workspace-dir=\/trusted\/workspace$/u); - assert.doesNotMatch(outputs.shell, /--source-mode/u); - assert.doesNotMatch(outputs.shell, /--source-repository-url/u); + assert.equal(outputs.verb, "shell"); + assert.equal( + outputs.args, + path.join(tempDir, "rush-delivery-action/dagger-shell"), + ); + const daggerShell = await readFile(outputs.args, "utf8"); + assert.match(daggerShell, /^repo=\$\(host \| directory /u); + assert.match(daggerShell, /--exclude='\*\*\/node_modules'/u); + assert.match(daggerShell, /--exclude='\*\*\/generated'/u); + assert.match(daggerShell, /--exclude='!apps\/api\/generated'/u); + assert.match(daggerShell, /rush_delivery_input_0=\$\(host \| file /u); + assert.match(daggerShell, /rush_delivery_input_1=\$\(host \| file /u); + assert.match(daggerShell, /rush_delivery_input_2=\$\(host \| directory /u); + assert.match(daggerShell, /rush_delivery_input_3=\$\(host \| unix-socket /u); + assert.match(daggerShell, /local-source --repo=\$repo \| workflow/u); + assert.match(daggerShell, /--host-workspace-dir=\/trusted\/workspace\n$/u); + assert.doesNotMatch(daggerShell, /--source-mode/u); + assert.doesNotMatch(daggerShell, /--source-repository-url/u); + assert.equal((await stat(outputs.args)).mode & 0o777, 0o600); await rm(tempDir, { force: true, recursive: true }); }); @@ -151,7 +170,7 @@ test("Git source mode never reads local-copy ignore settings", async () => { "[source-import] bounded local-copy settings are ignored in Git source mode", ); const outputs = parseGithubOutput(await readFile(outputPath, "utf8")); - assert.equal(outputs.shell, ""); + assert.equal(outputs.verb, "call"); assert.match(outputs.args, /--source-mode=git/u); await rm(tempDir, { force: true, recursive: true }); diff --git a/test/local-source-launcher.test.ts b/test/local-source-launcher.test.ts index b0e99a3..42a4e36 100644 --- a/test/local-source-launcher.test.ts +++ b/test/local-source-launcher.test.ts @@ -90,6 +90,65 @@ test("bounded launcher emits ordered caller-side filters and preserves inclusion rmSync(repository, { force: true, recursive: true }); }); +test("bounded launcher passes host paths as typed Dagger Shell objects", () => { + const repository = createRepository(); + const workflowEnv = path.join(repository, "workflow env"); + const deployEnv = path.join(repository, "deploy.env"); + const releaseEnv = path.join(repository, "release.env"); + const runtimeFiles = path.join(repository, "runtime files"); + const dockerSocket = path.join(repository, "docker.sock"); + const result = runLauncher([ + "--emit-shell", + `--repo=${repository}`, + "--", + "workflow", + `--workflow-env-file=${workflowEnv}`, + "--deploy-env-file", + deployEnv, + `--release-env-file=${releaseEnv}`, + "--runtime-files", + runtimeFiles, + `--docker-socket=${dockerSocket}`, + ]); + + assert.equal(result.status, 0, result.stderr); + assert.match( + result.stdout, + new RegExp( + `rush_delivery_input_0=\\$\\(host \\| file '${workflowEnv}'\\)`, + "u", + ), + ); + assert.match( + result.stdout, + new RegExp( + `rush_delivery_input_1=\\$\\(host \\| file '${deployEnv}'\\)`, + "u", + ), + ); + assert.match(result.stdout, /rush_delivery_input_2=\$\(host \| file /u); + assert.match(result.stdout, /rush_delivery_input_3=\$\(host \| directory /u); + assert.match( + result.stdout, + /rush_delivery_input_4=\$\(host \| unix-socket /u, + ); + for (const [name, index] of [ + ["workflow-env-file", 0], + ["deploy-env-file", 1], + ["release-env-file", 2], + ["runtime-files", 3], + ["docker-socket", 4], + ] as const) { + assert.match( + result.stdout, + new RegExp(`--${name}=\\$rush_delivery_input_${index}`, "u"), + ); + } + assert.doesNotMatch(result.stdout, /'--workflow-env-file=\//u); + + rmSync(repository, { force: true, recursive: true }); +}); + test("bounded launcher rejects unsafe patterns and mandatory path removal", () => { const invalidPatterns = [ "../outside", @@ -169,7 +228,7 @@ test("legacy launcher preserves the top-level call path without reading ignores" [ "--source-import-policy=legacy", `--repo=${repository}`, - "--module=github.com/example/rush-delivery@v0.9.0", + "--module=github.com/example/rush-delivery@v0.9.1", "--", "validate", "--event-name=pull_request", @@ -184,7 +243,7 @@ test("legacy launcher preserves the top-level call path without reading ignores" assert.deepEqual(readFileSync(capturePath, "utf8").trim().split("\n"), [ "call", "-m", - "github.com/example/rush-delivery@v0.9.0", + "github.com/example/rush-delivery@v0.9.1", "validate", "--event-name=pull_request", "--source-mode=local_copy", From e4d4d63ae42555513ef4a4ec19e74ffddae0f5e7 Mon Sep 17 00:00:00 2001 From: Artem Korolev Date: Thu, 6 Aug 2026 00:42:24 +0000 Subject: [PATCH 2/3] chore(release): prepare v0.9.1 compatibility patch Freeze the v0.9.0 documentation and schemas, advance current release pins and provenance to v0.9.1, add the patch upgrade guide, and record immutable-tag smoke evidence and corrective release gates. --- .github/workflows/release-smoke.yml | 2 +- README.md | 13 +- .../versioned_docs/version-v0.9.0/api.md | 196 ++++ .../version-v0.9.0/development.md | 150 +++ .../version-v0.9.0/entrypoints.md | 296 ++++++ .../version-v0.9.0/github-action.md | 314 +++++++ .../versioned_docs/version-v0.9.0/index.md | 10 + .../version-v0.9.0/introduction.md | 71 ++ .../local-copy-source-imports.md | 186 ++++ .../versioned_docs/version-v0.9.0/metadata.md | 418 +++++++++ .../oci-application-image-troubleshooting.md | 540 +++++++++++ .../version-v0.9.0/oci-application-images.md | 882 ++++++++++++++++++ .../version-v0.9.0/oci-registry-recipes.md | 729 +++++++++++++++ .../version-v0.9.0/providers.md | 153 +++ .../version-v0.9.0/quick-start/ci-cli.md | 144 +++ .../quick-start/github-actions.md | 157 ++++ .../version-v0.9.0/quick-start/local-run.md | 84 ++ .../version-v0.9.0/rush-toolchain.md | 131 +++ .../versioned_docs/version-v0.9.0/tutorial.md | 116 +++ .../tutorial/adapting-to-your-project.md | 153 +++ .../tutorial/dagger-metadata-map.md | 120 +++ .../version-v0.9.0/tutorial/deploy-mesh.md | 62 ++ .../version-v0.9.0/tutorial/deploy-targets.md | 136 +++ .../version-v0.9.0/tutorial/github-actions.md | 174 ++++ .../version-v0.9.0/tutorial/local-dry-runs.md | 116 +++ .../tutorial/mixed-node-python-toolchain.md | 125 +++ .../tutorial/npm-package-release-baseline.md | 104 +++ .../tutorial/oci-application-images.md | 121 +++ .../build-and-scan-target.md | 213 +++++ .../deploy-the-digest.md | 425 +++++++++ .../environment-profiles.md | 188 ++++ .../oci-application-images/github-actions.md | 278 ++++++ .../provider-off-dry-run.md | 159 ++++ .../publish-and-inspect.md | 459 +++++++++ .../registry-and-cosign-bootstrap.md | 464 +++++++++ .../split-stages-and-rollback.md | 529 +++++++++++ .../tutorial/package-release-workflow.md | 158 ++++ .../tutorial/package-targets.md | 157 ++++ .../tutorial/provider-artifacts.md | 141 +++ .../tutorial/release-metadata.md | 101 ++ .../version-v0.9.0/tutorial/rush-commands.md | 69 ++ .../tutorial/rush-monorepo-baseline.md | 75 ++ .../tutorial/validation-targets.md | 80 ++ .../version-v0.9.0/upgrade-v0-9-0.md | 122 +++ .../version-v0.9.0/workflows.md | 223 +++++ .../version-v0.9.0-sidebars.json | 224 +++++ docs-versions/versions.json | 1 + docs/README.md | 4 +- docs/api.md | 2 +- docs/development.md | 4 +- docs/entrypoints.md | 2 +- docs/github-actions.md | 10 +- docs/local-copy-source-imports.md | 19 +- docs/metadata.md | 10 +- docs/oci-application-image-troubleshooting.md | 2 +- docs/oci-application-images.md | 14 +- docs/oci-registry-recipes.md | 22 +- docs/quick-start/ci-cli.md | 6 +- docs/quick-start/github-actions.md | 6 +- docs/quick-start/local-run.md | 6 +- docs/rush-toolchain.md | 8 +- docs/tutorial/05-package-targets.md | 2 +- docs/tutorial/09-github-actions.md | 8 +- docs/tutorial/10-local-dry-runs.md | 10 +- docs/tutorial/11-adapting-to-your-project.md | 2 +- docs/tutorial/13-release-metadata.md | 2 +- docs/tutorial/14-package-release-workflow.md | 8 +- .../15-mixed-node-python-toolchain.md | 10 +- .../01-build-and-scan-target.md | 4 +- .../02-provider-off-dry-run.md | 2 +- .../03-registry-and-cosign-bootstrap.md | 4 +- .../06-github-actions.md | 10 +- .../07-split-stages-and-rollback.md | 2 +- .../08-environment-profiles.md | 8 +- .../tutorial/oci-application-images/README.md | 18 +- docs/upgrade-v0.9.0.md | 8 +- docs/upgrade-v0.9.1.md | 94 ++ docs/workflows.md | 8 +- .../README.md | 2 +- .../application-image-providers.yaml | 2 +- .../rush-toolchain.yaml | 2 +- .../.dagger/application-images/providers.yaml | 2 +- .../.dagger/deploy/services-mesh.yaml | 2 +- .../deploy/targets/control-plane-api.yaml | 2 +- .../package/targets/control-plane-api.yaml | 2 +- .../.dagger/rush-cache/providers.yaml | 2 +- .../application-image-providers.schema.json | 107 +++ .../v0.9.1/deploy-services-mesh.schema.json | 28 + schemas/v0.9.1/deploy-target.schema.json | 191 ++++ schemas/v0.9.1/npm-release.schema.json | 102 ++ schemas/v0.9.1/package-manifest.schema.json | 281 ++++++ schemas/v0.9.1/package-target.schema.json | 196 ++++ .../v0.9.1/rush-cache-providers.schema.json | 93 ++ schemas/v0.9.1/rush-toolchain.schema.json | 79 ++ .../toolchain-image-providers.schema.json | 54 ++ schemas/v0.9.1/validation-target.schema.json | 121 +++ src/application-images/package-image.ts | 4 +- ...RY_DEPLOYMENT_ENVIRONMENT_COMPATIBILITY.md | 74 +- ...-environment-compatibility-example.test.ts | 6 +- test/documentation-contract.test.ts | 12 +- .../application-image-providers.yaml | 2 +- .../fixtures/oci-contract/package-target.yaml | 2 +- test/metadata-schemas.test.ts | 51 +- test/oci-acceptance-harness.test.ts | 4 +- test/oci-example.test.ts | 6 +- test/release-smoke-workflow.test.ts | 4 +- test/scripts/verify-oci-acceptance.mjs | 6 +- website-docusaurus/docs-tree.yaml | 4 + website-docusaurus/docusaurus.config.ts | 3 +- .../scripts/sync-versioned-docs.mjs | 1 + website-docusaurus/src/pages/index.tsx | 8 +- website/docs-tree.yaml | 4 + website/src/pages/index.astro | 8 +- 113 files changed, 11381 insertions(+), 170 deletions(-) create mode 100644 docs-versions/versioned_docs/version-v0.9.0/api.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/development.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/entrypoints.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/github-action.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/index.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/introduction.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/local-copy-source-imports.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/metadata.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/oci-application-image-troubleshooting.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/oci-application-images.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/oci-registry-recipes.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/providers.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/quick-start/ci-cli.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/quick-start/github-actions.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/quick-start/local-run.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/rush-toolchain.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/adapting-to-your-project.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/dagger-metadata-map.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-mesh.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-targets.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/github-actions.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/local-dry-runs.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/mixed-node-python-toolchain.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/npm-package-release-baseline.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/build-and-scan-target.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/deploy-the-digest.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/environment-profiles.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/github-actions.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/provider-off-dry-run.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/publish-and-inspect.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/registry-and-cosign-bootstrap.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/split-stages-and-rollback.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/package-release-workflow.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/package-targets.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/provider-artifacts.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/release-metadata.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-commands.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-monorepo-baseline.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/tutorial/validation-targets.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/upgrade-v0-9-0.md create mode 100644 docs-versions/versioned_docs/version-v0.9.0/workflows.md create mode 100644 docs-versions/versioned_sidebars/version-v0.9.0-sidebars.json create mode 100644 docs/upgrade-v0.9.1.md create mode 100644 schemas/v0.9.1/application-image-providers.schema.json create mode 100644 schemas/v0.9.1/deploy-services-mesh.schema.json create mode 100644 schemas/v0.9.1/deploy-target.schema.json create mode 100644 schemas/v0.9.1/npm-release.schema.json create mode 100644 schemas/v0.9.1/package-manifest.schema.json create mode 100644 schemas/v0.9.1/package-target.schema.json create mode 100644 schemas/v0.9.1/rush-cache-providers.schema.json create mode 100644 schemas/v0.9.1/rush-toolchain.schema.json create mode 100644 schemas/v0.9.1/toolchain-image-providers.schema.json create mode 100644 schemas/v0.9.1/validation-target.schema.json diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml index b368c79..7df8a62 100644 --- a/.github/workflows/release-smoke.yml +++ b/.github/workflows/release-smoke.yml @@ -7,7 +7,7 @@ on: target_ref: description: Released tag or immutable ref to verify. required: true - default: v0.9.0 + default: v0.9.1 type: string expected_commit: description: Expected full peeled commit SHA for target_ref. diff --git a/README.md b/README.md index 84e906f..e0a1b74 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -80,7 +80,7 @@ jobs: service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - name: Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: dry-run: "false" environment: prod @@ -110,7 +110,7 @@ source SHA; Rush package release pushes its generated version commit to the metadata `target_branch`. ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: dry-run: "false" release-targets-json: '["npm"]' @@ -150,7 +150,7 @@ jobs: permissions: contents: write steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" @@ -172,7 +172,7 @@ This mode clones the target repository inside Dagger, so the CI runner does not need to mount the repository into the module. ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 RUNTIME_FILES_DIR="${RUNNER_TEMP}/rush-delivery-runtime-files" WORKFLOW_ENV_FILE="${RUNNER_TEMP}/dagger-workflow.env" DEPLOY_ENV_FILE="${RUNNER_TEMP}/dagger-deploy.env" @@ -240,7 +240,7 @@ your latest changes. ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ workflow \ @@ -269,6 +269,7 @@ installation, inclusion rules, and the `legacy` recovery path. - [Provider adapters](docs/providers.md) - [Bounded local-copy imports](docs/local-copy-source-imports.md) - [Project-owned Rush toolchain](docs/rush-toolchain.md) +- [Upgrade to v0.9.1](docs/upgrade-v0.9.1.md) - [Upgrade to v0.9.0](docs/upgrade-v0.9.0.md) - [OCI application images tutorial](docs/tutorial/oci-application-images/README.md) - [OCI application images](docs/oci-application-images.md) diff --git a/docs-versions/versioned_docs/version-v0.9.0/api.md b/docs-versions/versioned_docs/version-v0.9.0/api.md new file mode 100644 index 0000000..c7592be --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/api.md @@ -0,0 +1,196 @@ +--- +id: "api" +title: "Public API" +sidebar_label: "Public API" +--- + +When consuming this module from CI, prefer Git source mode so Dagger clones the +Rush repository internally. For a checked-out worktree, use the versioned +`rush-delivery-local` launcher so exclusions apply before source transfer. + +```sh +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +``` + +GitHub Actions can use the root action wrapper instead of assembling the raw +command. See [GitHub Action usage](../github-action). + +## Entrypoints + +`workflow` is the normal release orchestrator. It resolves source, validates +metadata, computes the CI plan, builds selected deploy targets, packages their +artifacts, deploys them in dependency order, and can compose selected package +release targets. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call workflow \ + --git-sha="$GIT_SHA" \ + --event-name=push \ + --dry-run=false \ + --workflow-env-file="$WORKFLOW_ENV_FILE" \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --release-targets-json='["npm"]' \ + --release-env-file="$RELEASE_ENV_FILE" \ + --runtime-files="$RUNTIME_FILES_DIR" \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +`self-check` is the framework health check. It runs the Dagger module +typecheck and unit tests from this repository. + +```sh +dagger call self-check +``` + +`validate` runs pull-request validation for affected Rush projects, +target-specific validation metadata, and release-readiness checks when +`.dagger/release/npm.yaml` is configured. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call validate \ + --git-sha="$GIT_SHA" \ + --event-name=pull_request \ + --pr-base-sha="$PR_BASE_SHA" \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --toolchain-image-provider=github \ + --rush-cache-provider=github \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +For local validation against unpushed changes, use the bounded launcher from +the [local-copy guide](../local-copy-source-imports). + +`releasePackages` runs the package release/versioning flow from +`.dagger/release/npm.yaml`. The first supported strategy is Rush change-file +publishing for npm packages. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call release-packages \ + --git-sha="$GIT_SHA" \ + --dry-run=false \ + --release-env-file="$RELEASE_ENV_FILE" \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +Live package releases require Git source mode with write credentials. Rush +Delivery runs the shared Rush lifecycle in build-first order (`build`, `lint`, +`test`, `verify`), lets Rush apply the change files, publishes packages, and +pushes the version commit back to the configured target branch. Dry-runs run the +same planning path without pushing commits, tags, or packages. + +`releasePackages` uses a release-scoped metadata contract. It requires Rush +project metadata and `.dagger/release/npm.yaml`, but it does not require deploy +metadata. Rush cache provider metadata is only required when the selected Rush +cache provider is not `off`. + +The release env file must contain the npm token named by +`.dagger/release/npm.yaml` and the Git token named by `sourceAuthTokenEnv` for +live Git source releases. + +See [Entrypoints reference](../entrypoints) for every callable function, +including separate `detect`, `build`, `package`, `deploy`, metadata validation, +and diagnostic entrypoints. + +## Key Inputs + +`repo` is the caller's Rush repository directory for `sourceMode=local_copy`. +Git source mode does not require it. Existing top-level entrypoints keep their +released static filter. `localSource(repo)` is the additive object used by the +launcher after it has composed an already-filtered Directory; its constructor +does not apply a second filter. + +`gitSha` is the commit being validated or released. It is required for Git +source mode. + +`eventName`, `forceTargetsJson`, `prBaseSha`, and `deployTagPrefix` shape +detection. Forced targets are used by manual deploy wrappers. + +`deployEnvFile` is a newline-delimited environment file for workflow, validate, +build, and deploy paths. The framework reads it once, then passes only package- +or deploy-target-allowed variables to build and runtime containers. +Application-image publishing may resolve the selected provider's public +registry coordinates and protected registry/signing values from the +workflow-plus-deploy overlay. Coordinate values remain ordinary routing data; +credentials become Dagger secrets and neither class is projected to project +code. Deploy receives only the packaged digest handoff. + +`workflowEnvFile` is a newline-delimited environment file shared by the +composed `workflow`. Use it for source/provider values that may be needed +before a stage-specific overlay is selected. `deployEnvFile` and +`releaseEnvFile` may repeat a workflow env key only with the same value. + +`releaseEnvFile` is a newline-delimited environment file for package release. +It carries package release credentials such as `NPM_TOKEN`. In the composed +`workflow`, release metadata decides which values reach the package release +container. In standalone `releasePackages`, the same file also carries source +write credentials such as `GITHUB_TOKEN`. + +`releaseTargetsJson` selects package release targets for `workflow`. +Currently `["npm"]` is supported. The default `[]` keeps deploy-only workflow +behavior unchanged. + +`runtimeFiles` is an optional directory of deploy-platform files such as cloud +credentials, kubeconfig files, or generated deployment certificates. Deploy +target metadata can mount files from this bundle without making them part of +source, package artifacts, Rush install cache, or toolchain image hashes. Do +not put OCI registry tokens, Cosign private keys, signing passwords, or Cosign +public keys there; application-image credentials are Package-only environment +inputs selected by provider metadata. + +`sourceMode` is `git` or `local_copy`. Git mode is the recommended CI path and +uses provider-neutral source coordinates. Local-copy mode needs `repo` and is +intended for local tests, offline runs, and unpushed changes. The Action adds +`source-import-policy` (`bounded` by default, `legacy` for recovery) and +`source-import-ignore-file`. The portable launcher exposes equivalent flags. + +`toolchainImageProvider` and `rushCacheProvider` are `off` by default. Provider +`github` enables GHCR-backed toolchain images or Rush install cache. +Optional `.dagger/toolchains/rush.yaml` extends the Rush workflow image with +digest-pinned, checksummed executables. Its absence preserves the exact default +toolchain identity. See the [toolchain guide](../rush-toolchain). + +`applicationImageProvider` is `off` by default. A live selection containing an +`oci_image` package target must choose a provider declared in +`.dagger/application-images/providers.yaml`. Named-provider dry runs validate +repository intent without requiring or resolving provider credentials. A +supplied aggregate env file is still parsed for other configured capabilities; +omit live OCI values from dry/no-OCI calls. Filesystem-only projects do not +need the metadata or a configuration change after upgrading; when no selected +target is OCI, workflow/package planning ignores the application provider +input, provider file, and provider credentials. +Provider coordinates may be static or selected by `registry_env` and +`repository_prefix_env`. Named dry runs resolve only the public coordinate +values; live credentials remain deferred until Package is ready. See the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles). + +For `workflow`, `toolchainImagePolicy` and `rushCachePolicy` default to `lazy`, +which is the trusted release behavior: pull first, build or install on miss, and +publish refreshed provider artifacts after success. For `validate`, both +policies default to `pull-or-build`, which pulls existing artifacts and builds +or installs locally on miss without publishing. + +`dockerSocket` is an optional compatibility input for project-owned deploy +targets that invoke Docker. First-class OCI package artifacts use Dagger-native +build and publication and do not require it. + +## Defaults + +Local defaults favor portability: provider-off, dry-run enabled, and +`local_copy` source mode. CI should opt into provider adapters explicitly. + +For OCI adoption, follow the +[OCI application images tutorial](../tutorial/oci-application-images), +then use the [production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting). diff --git a/docs-versions/versioned_docs/version-v0.9.0/development.md b/docs-versions/versioned_docs/version-v0.9.0/development.md new file mode 100644 index 0000000..dff7532 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/development.md @@ -0,0 +1,150 @@ +--- +id: "development" +title: "Development" +sidebar_label: "Development" +description: "Maintain this repository and generated documentation." +--- + +This page is for maintaining the Rush Delivery repository itself. User-facing +setup lives in the [Quick Start](../quick-start/github-actions). + +## Local Checks + +Run the Dagger self-check before changing metadata, schemas, or module source: + +```sh +dagger call self-check +``` + +Run the TypeScript and test suite from the repository root: + +```sh +npm run typecheck +npm test +``` + +OCI application-image changes also need the live disposable-registry +acceptance path. It creates temporary Cosign keys, publishes a short-lived +scratch image, and verifies the digest manifest and evidence: + +```sh +test/scripts/run-oci-acceptance.sh +``` + +The acceptance script requires Dagger but no host Docker or Podman CLI, socket, +or daemon. It creates temporary Cosign material and performs framework image +build/publication through Dagger. +The Package implementation pins Syft 1.50.0, Grype 0.116.1, and Cosign 3.1.2 by +immutable image digest. Update each version and digest together, then rerun the +unit, Dagger self-check, and live acceptance paths. Cosign `3.1.2` also pins the +deprecated `--new-bundle-format=false` registry-storage contract. A Cosign bump +must first prove the replacement CLI flags, signature/attestation storage, +independent three-way verification, full tagged/untagged cleanup, and exact live +registry acceptance; never update only the version/digest and assume compatible +artifact semantics. + +The pinned Dagger `v0.20.7` engine requires `Container.withExec` stdout +redirection to resolve to a writable regular file in the container filesystem. +It rejects `redirectStdout: "/dev/null"` before starting the command with +`Error: open redirect stdout file: cannot resolve path "/dev/null"`. Keep the +six registry Cosign commands on their distinct +`/tmp/rush-delivery-cosign-*.stdout` sinks. Those files exist only in the +ephemeral Cosign container and are neither exported nor retained as release +evidence. Engine or Cosign upgrades must retain the engine regression proving a +real regular-file redirect works; a failure at a named Cosign stage does not by +itself prove that the Cosign process started. + +Every public Dagger function must declare an intentional cache scope. Calls that +observe mutable external state, execute project code, or create side effects use +`cache: "never"`; inspection-only functions may use session caching. Because +that setting does not disable container layer caching, Cosign preflight and +publication, Grype scans, Deploy scripts, and npm release also receive a fresh +non-secret execution input. Keep those checks aligned with Dagger's official +[function-caching](https://docs.dagger.io/extending/function-caching/) and +[secret-handling](https://docs.dagger.io/extending/secrets/) guidance. Never +write raw or derived credentials into a container filesystem layer. + +## Website Checks + +The public GitHub Pages site currently builds from +[`../website-docusaurus`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website-docusaurus). It uses Docusaurus, generates +docs pages from `website-docusaurus/docs-tree.yaml`, and is deployed by +[`../.github/workflows/pages.yml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/.github/workflows/pages.yml). + +The two sites have independent lockfiles and are not root Yarn workspaces. From +a clean checkout, install each site exactly before running its checks: + +```sh +npm ci --prefix website +npm ci --prefix website-docusaurus +``` + +```sh +npm run site:docusaurus:check +npm run site:docusaurus:build +``` + +The Astro + Starlight comparison site remains under [`../website`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website). + +```sh +npm run site:check +npm run site:build +``` + +## Generated Site Inputs + +The root [`docs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs) directory is the source of truth for generated website +docs. When adding or renaming public docs pages, update both: + +- [`../website-docusaurus/docs-tree.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website-docusaurus/docs-tree.yaml) +- [`../website/docs-tree.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website/docs-tree.yaml) + +Schemas under [`../schemas`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas) are copied into the static site during +website builds and are published under `/rush-delivery/schemas/`. Exact release +schemas also live under versioned subdirectories such as +`/rush-delivery/schemas/v0.9.0/`. + +When releasing a version that changes schema behavior, create a new versioned +schema snapshot such as `schemas/v0.9.0`, keep earlier directories immutable, +and update the root schemas to the current release shape. + +## Versioned Docusaurus Docs + +Docusaurus is the canonical versioned documentation site. The current editable +docs stay in [`docs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs), while released snapshots are committed under +[`../docs-versions`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs-versions). + +Docusaurus expects `versions.json`, `versioned_docs`, and `versioned_sidebars` +inside the website directory, so +[`../website-docusaurus/scripts/sync-versioned-inputs.mjs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website-docusaurus/scripts/sync-versioned-inputs.mjs) +copies the canonical root snapshots into Docusaurus-local generated inputs +before `start`, `build`, and `check`. + +After a docs-bearing release: + +1. Update the current docs version in + [`../website-docusaurus/docusaurus.config.ts`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website-docusaurus/docusaurus.config.ts). +2. Add the previous current version to `publishedVersions` in + [`../website-docusaurus/scripts/sync-versioned-docs.mjs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/website-docusaurus/scripts/sync-versioned-docs.mjs) + when the release changed public docs. +3. Run: + + ```sh + npm --prefix website-docusaurus run sync-versioned-docs + npm --prefix website-docusaurus run sync-versioned-inputs + npm run site:docusaurus:check + ``` + +4. Confirm the generated versioned docs and sidebars match the released tag. + +When preparing documentation for the next release line, snapshot the latest +released documentation before editing root [`docs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs). In practice, finish and +tag the release, run the versioned docs sync so `docs-versions` contains a +directory for that released tag, and only then update current docs for the next +version. This keeps published docs stable for users pinned to older module +versions. + +Patch releases do not need a new docs snapshot when user-facing docs did not +change. Versioned docs should point users at exact versioned schema URLs where +editor stability matters, while root schema URLs continue to track the current +release. diff --git a/docs-versions/versioned_docs/version-v0.9.0/entrypoints.md b/docs-versions/versioned_docs/version-v0.9.0/entrypoints.md new file mode 100644 index 0000000..1a32fbd --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/entrypoints.md @@ -0,0 +1,296 @@ +--- +id: "entrypoints" +title: "Entrypoints" +sidebar_label: "Entrypoints" +--- + +When consuming this module from CI, prefer Git source mode so Dagger clones the +Rush repository internally: + +```sh +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +``` + +## `workflow` + +The main release composition. It resolves source, validates metadata, detects +deploy targets, selects explicit package release targets, builds, packages, and +then runs deploy and package-release side effects. + +Use it for normal CI release runs and local release dry-runs. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call workflow \ + --git-sha="$GIT_SHA" \ + --event-name=push \ + --dry-run=false \ + --workflow-env-file="$WORKFLOW_ENV_FILE" \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --release-targets-json='["npm"]' \ + --release-env-file="$RELEASE_ENV_FILE" \ + --runtime-files="$RUNTIME_FILES_DIR" \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +When `release-targets-json` is empty, returns the existing text deployment +summary. When package release targets are selected, returns a combined summary +with `deploy` and `release_packages` sections. + +Deploy and package release side effects run after shared prerequisites. They +can run concurrently and are not transactional across external systems. + +For local runs against a checked-out working tree, use `rush-delivery-local` +from the [bounded local-copy guide](../local-copy-source-imports). + +## `validate` + +Runs Dagger-owned validation for affected Rush projects. It can also run +target-specific validation metadata such as backing services, migrations, server +startup, and smoke tests. + +Use it for pull-request validation paths or local validation experiments. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call validate \ + --git-sha="$GIT_SHA" \ + --event-name=pull_request \ + --pr-base-sha="$PR_BASE_SHA" \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --toolchain-image-provider=github \ + --rush-cache-provider=github \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +Returns a validation summary. + +For local runs against a checked-out working tree, use `rush-delivery-local` +from the [bounded local-copy guide](../local-copy-source-imports). + +## `release-packages` + +Runs package release/versioning from `.dagger/release/npm.yaml`. The initial +strategy is npm publishing through Rush change files. + +Use it for standalone package release workflows, package-only repositories, and +release debugging. The entrypoint runs the shared Rush lifecycle in build-first +order (`build`, `lint`, `test`, `verify`), lets Rush apply change files, +publishes packages, and pushes the generated version commit. For live releases, +Rush Delivery prepares the metadata target branch locally before `rush publish` +so Rush can check it out for the final merge. It does not touch deploy tags. + +NPM provenance defaults to `false`; opt in from `.dagger/release/npm.yaml` only +when the release runtime is wired for supported npm provenance detection. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call release-packages \ + --git-sha="$GIT_SHA" \ + --dry-run=false \ + --release-env-file="$RELEASE_ENV_FILE" \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` + +Use `toolchain-image-provider=github` or `rush-cache-provider=github` only when +the repository has matching provider metadata and the CI job has package +registry permissions. + +For local dry-runs against a checked-out working tree, use +`rush-delivery-local ... -- release-packages` and keep `--dry-run=true`. + +## `local-source` + +Returns an additive Dagger object with `workflow`, `validate`, and +`release-packages` functions over a caller-composed `repo` Directory. It is the +module boundary used by `rush-delivery-local` and the bounded GitHub Action +path. The constructor applies no static ignores, so ordered caller re-inclusions +survive. + +```sh +repo=$(host | directory /workspace/project --exclude='**/node_modules') +local-source --repo=$repo | validate --event-name=pull_request +``` + +The snippet is Dagger Shell, not a host shell. Prefer the release launcher, +which validates and quotes paths/patterns and verifies `.git`, `.dagger`, and +`rush.json` before delegating. The old top-level functions remain the +legacy-compatible direct-call API. + +## `detect` + +Computes the canonical CI plan JSON. The plan includes mode, validation targets, +deploy targets, and affected projects by deploy target. + +Use it when a CI provider intentionally runs split stages. The `workflow` +entrypoint already calls it internally. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call detect \ + --repo=. \ + --event-name=push \ + --force-targets-json='[]' \ + --deploy-tag-prefix=deploy/prod +``` + +Returns JSON intended for Dagger stage handoff. + +## `build-deploy-targets` + +Runs the generic Rush build stage for deploy targets selected by a CI plan file. + +Use it only in split-stage workflows where build is separated from package and +deploy. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call build-deploy-targets \ + --repo=. \ + --ci-plan-file="$CI_PLAN_FILE" \ + --deploy-env-file="$DEPLOY_ENV_FILE" +``` + +Returns a Dagger directory containing the built workspace. `deploy-env-file` is +optional, but required when selected package targets declare build-time +`pass_env` or `map_env` values without dry-run defaults. Pass `--dry-run=true` +when you want build-time env to use package target `dry_run_defaults`. + +## `package-deploy-targets` + +Materializes deploy artifacts for targets selected by a CI plan file. Package +behavior is driven by `.dagger/package/targets`. + +Use it only in split-stage workflows after build outputs already exist. Treat +that built directory and its provider/deploy metadata as trusted Package input: +this entrypoint can freeze only the credential-name boundary present when it is +invoked. Prefer `build-and-package-deploy-targets` when Build could modify +metadata, because the combined producer captures the boundary before Build. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call package-deploy-targets \ + --repo=. \ + --ci-plan-file="$CI_PLAN_FILE" \ + --artifact-prefix=deploy-target \ + --git-sha="$GIT_SHA" \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --dry-run=false \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --application-image-provider=off +``` + +Returns a Dagger directory containing packaged artifacts, a package manifest, +and OCI evidence when selected. It accepts the same build-time +`deploy-env-file` and `dry-run` inputs as `build-deploy-targets`. OCI targets +also use `git-sha`, the optional source URL, and the selected application-image +provider. Directory/archive-only calls remain valid without those additions. + +## `build-and-package-deploy-targets` + +Runs build and package as separate logical stages, then exports the final +packaged workspace once. + +Use it when a split workflow needs build and package together but deploy later. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file="$CI_PLAN_FILE" \ + --artifact-prefix=deploy-target \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --git-sha="$GIT_SHA" \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --application-image-provider=off +``` + +Returns a Dagger directory containing packaged artifacts and a package manifest. +For OCI targets, Package performs registry publication and carries the verified +manifest/evidence into the returned directory; Deploy later resolves the image +from the registry by digest. + +The commands above are filesystem-first examples. For a live OCI target, +configure the package target and application-image provider together, then +replace `off` with that provider name. Follow the +[OCI application images tutorial](../tutorial/oci-application-images) +before using the lower-level split-stage APIs. + +## `deploy-release` + +Deploys selected targets from an already packaged workspace. It executes deploy +targets in service-mesh wave order and can use a package manifest to resolve +artifact paths. + +Use it for split-stage workflows, deploy-only retries, or tests around deploy +metadata. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call deploy-release \ + --repo=. \ + --git-sha="$GIT_SHA" \ + --release-targets-json='["server","webapp"]' \ + --environment=prod \ + --dry-run=false \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --runtime-files="$RUNTIME_FILES_DIR" \ + --package-manifest-file="$PACKAGE_MANIFEST_FILE" +``` + +Returns a text deployment summary. + +## `self-check` + +Runs the framework health check: Dagger module typecheck and unit tests. + +Use it before changing framework source, schemas, or docs. + +```sh +dagger call self-check +``` + +Returns a self-check summary. + +## `validate-metadata-contract` + +Checks cross-file metadata consistency without running release stages. + +Use it when editing `.dagger/` metadata and wanting a fast contract check. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call validate-metadata-contract --repo=. +``` + +Returns formatted metadata contract JSON. + +## `describe-release-targets` + +Validates and normalizes a release target JSON array. + +Use it for quick checks around manual target input. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call describe-release-targets \ + --release-targets-json='["server"]' +``` + +Returns a short text description. + +For OCI-specific package/deploy behavior, use the +[production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting). + +## `ping` + +Returns a simple readiness marker. + +Use it only to verify that the module is callable. + +```sh +dagger -m "$RUSH_DELIVERY_MODULE" call ping +``` diff --git a/docs-versions/versioned_docs/version-v0.9.0/github-action.md b/docs-versions/versioned_docs/version-v0.9.0/github-action.md new file mode 100644 index 0000000..e5e7bcd --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/github-action.md @@ -0,0 +1,314 @@ +--- +id: "github-action" +title: "GitHub Action" +sidebar_label: "GitHub Action" +description: "Use Rush Delivery directly from GitHub Actions." +--- + +Rush Delivery can be used as a GitHub Action or as a raw Dagger module. The +GitHub Action is a thin adapter over the module's Dagger functions, so release +and validation behavior stay identical between action and raw CLI usage. + +## Pull Request Validation + +Use `entrypoint: validate` for PR CI. The action defaults to Git source mode, +uses the current GitHub repository and ref, writes `GITHUB_TOKEN` into the +deploy env file for source authentication, and forwards the pull request base +SHA from the GitHub event. When `entrypoint: validate` is selected, provider +policies default to `pull-or-build`, so existing toolchain images and Rush cache +can be reused without granting publish access. If npm release metadata exists, +validation also runs Rush change-file verification. + +```yaml +name: ci-validate + +on: + pull_request: + +permissions: + contents: read + packages: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + toolchain-image-provider: github + rush-cache-provider: github +``` + +`pull-or-build` pulls the provider artifact when it exists. If it is missing, +validation builds locally inside the current Dagger run and does not publish to +GHCR. + +If selected package targets declare build-time `pass_env` or `map_env`, include +those source values in `deploy-env` for PR validation too. Keep PR values +read-only and avoid granting publish credentials. + +To validate unpushed local-copy source from a checked-out runner workspace, +override the source mode and pass `repo`: + +```yaml +steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + fetch-depth: 0 + + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + repo: . + source-mode: local_copy + source-import-policy: bounded + source-import-ignore-file: .dagger/source-import.ignore +``` + +`bounded` is the v0.9.0 local-copy default. It removes dependency/cache trees +at the Dagger host import operation while retaining `.git`, `.dagger`, and +`rush.json`. Repository `!` inclusions are read from the optional ignore file. +Use `legacy` only as a temporary recovery path for a required matched file. Git +source mode never reads either local-copy input and emits one fixed diagnostic. +See [bounded local-copy imports](../local-copy-source-imports). + +## Release Workflow + +Provider authentication stays in the caller workflow. Pass shared values through +`workflow-env`, generated files through `runtime-file-map`, build or deploy +values through `deploy-env`, and package release values through `release-env`. +The `.dagger` metadata still decides which values reach each stage. + +```yaml +steps: + - id: auth + name: Authenticate to Google Cloud + if: inputs.force_targets_json != '["webapp"]' + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} + + - name: Rush Delivery + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + force-targets-json: ${{ inputs.force_targets_json || '[]' }} + deploy-tag-prefix: ${{ env.DEPLOY_TAG_PREFIX }} + artifact-prefix: ${{ env.DEPLOY_ARTIFACT_PREFIX }} + environment: prod + dry-run: "false" + toolchain-image-provider: ${{ env.TOOLCHAIN_IMAGE_PROVIDER }} + toolchain-image-policy: ${{ env.TOOLCHAIN_IMAGE_POLICY }} + rush-cache-provider: ${{ env.RUSH_CACHE_PROVIDER }} + rush-cache-policy: ${{ env.RUSH_CACHE_POLICY }} + release-targets-json: '["npm"]' + runtime-file-map: | + ${{ steps.auth.outputs.credentials_file_path }}=>gcp-credentials.json + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} + deploy-env: | + GCP_PROJECT_ID=${{ vars.GCP_PROJECT_ID }} + GCP_ARTIFACT_REGISTRY_REPOSITORY=${{ vars.GCP_ARTIFACT_REGISTRY_REPOSITORY }} + CLOUD_RUN_SERVICE=${{ vars.CLOUD_RUN_SERVICE }} + CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT=${{ vars.CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT }} + CLOUD_RUN_CORS_ORIGIN=${{ vars.CLOUD_RUN_CORS_ORIGIN }} + CLOUD_RUN_REGION=${{ env.CLOUD_RUN_REGION }} + CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + CLOUDFLARE_PAGES_PROJECT_NAME=${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME }} + WEBAPP_VITE_GRAPHQL_HTTP=${{ vars.WEBAPP_VITE_GRAPHQL_HTTP }} + WEBAPP_VITE_GRAPHQL_WS=${{ vars.WEBAPP_VITE_GRAPHQL_WS }} + WEBAPP_URL=https://${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME }}.pages.dev +``` + +`release-targets-json` is explicit because npm package release creates external +side effects: published packages and a Rush-generated version commit. When npm +release is selected, Rush Delivery runs the all-project Rush lifecycle once, +then starts deploy and package release side effects after shared prerequisites +have passed. These side effects are concurrent but not transactional; if one +external system fails after the other succeeds, the successful side effect may +already exist. + +For `workflow`, the action appends `GITHUB_ACTOR`, `GITHUB_REPOSITORY`, +`GITHUB_API_URL`, and `GITHUB_TOKEN` to the generated `workflow-env` file by +default. Set `include-github-env: "false"` if you want to provide those values +yourself. `deploy-env` and `release-env` may repeat workflow values only when +the value is identical. + +When deploy-tag updates are enabled, `GITHUB_API_URL` must be an absolute, +credential-free HTTPS base. GitHub Enterprise paths such as +`https://github.example.com/api/v3` are supported; embedded userinfo, HTTP, +query strings, and fragments are rejected before the bearer token is sent. +Failures report only the fixed action and HTTP status, never the remote response +body, because an endpoint could reflect authorization material. + +This release example is filesystem-first: it does not select an +application-image provider and does not require OCI registry or Cosign +credentials. + +## OCI Application Images + +Set `application-image-provider` to a provider declared in +`.dagger/application-images/providers.yaml` when a live release selects an +`oci_image` package target. The action default is `off`, so existing +directory/archive projects need no configuration change when upgrading. + +Provider metadata names public registry coordinates (or the environment names +that select them) and protected Cosign/registry credential environment names. +Put their values in `workflow-env` or `deploy-env`; the action passes the flat +env file to Dagger. Rush Delivery treats coordinates as public routing inputs +and converts only credential values to protected capabilities. +Store multiline PEM values with literal `\n` separators. Do not put registry or +signing values in `runtime-file-map`: deploy scripts receive only the verified +digest reference and target-scoped evidence. + +OCI image builds and publication are Dagger-native. Set `docker-socket: ""` in +OCI-only Action jobs; the non-empty Action default exists only for legacy +project deploy scripts that invoke Docker. A mounted host socket gives that +project code effective control of the runner's Docker daemon and can bypass +Dagger workspace and secret-file isolation by mounting host paths. Keep it only +for trusted legacy deploy scripts, never untrusted checkout code. +Registry-specific login steps are also unnecessary when the metadata-selected +username/token can push to the configured registry. + +Dry runs may leave `application-image-provider: off`, or select a named provider +to validate the planned repository without resolving its credentials. Build +the metadata and CI path with the +[OCI application images tutorial](../tutorial/oci-application-images), +the [environment-profile tutorial](../tutorial/oci-application-images/environment-profiles), +then use the [production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting). + +## Project-Owned Rush Tools + +When `.dagger/toolchains/rush.yaml` exists, every Rush-using entrypoint receives +its digest-pinned, checksummed executables before Rush install and lifecycle +scripts. No new Action input is required. Toolchain provider/cache inputs keep +their existing meaning; the project metadata becomes part of the v2 toolchain +cache identity. + +Trusted workflows may use `toolchain-image-policy: lazy` to publish a missing +content-addressed image. PR validation should use `pull-or-build` so it never +publishes. Follow the [toolchain guide](../rush-toolchain) and +[mixed Node/Python tutorial](../tutorial/mixed-node-python-toolchain). + +## Package Release + +Use `entrypoint: release-packages` when npm package release should stay as a +standalone workflow, for example in package-only repositories or release +debugging. Keep npm credentials in `release-env`, not `deploy-env`; package +release credentials are separate from deploy credentials because npm publishing +is a registry side effect, not a deploy target runtime. + +The smallest package-only workflow can keep provider adapters off. This is the +shape used by package-only projects such as +[LabKit](https://github.com/BootstrapLaboratory/labkit): + +```yaml +name: package-release + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + release-packages: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: release-packages + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +The package release entrypoint uses Git source mode by default, runs the shared +Rush lifecycle in build-first order (`build`, `lint`, `test`, `verify`), lets +Rush apply change files, publishes packages, and pushes the generated version +commit to the metadata `target_branch`. It prepares that target branch locally +before invoking `rush publish`, which lets Rush check it out for the final +merge. + +`contents: write` is required for live releases because Rush writes a generated +version commit and pushes it back to `versioning.target_branch`. The action +adds `GITHUB_TOKEN` to the generated release env file by default, so the same +token is used for source acquisition and the final push. Set +`include-github-env: "false"` only when you provide an equivalent token yourself. + +`packages: write` is not required for npmjs publishing by itself. Add +`packages: read` or `packages: write` only when `toolchain-image-provider` or +`rush-cache-provider` uses `github`. + +The project still owns npm publish policy through Rush and npm files: + +- `common/config/rush/version-policies.json` and `rush.json` decide package + version policy names. +- Rush change files decide the next version and changelog content. +- Package `publishConfig`, `files`, entrypoints, and private/public package + settings decide what npm can publish. +- `common/config/rush/.npmrc-publish` maps `NPM_TOKEN` into npm auth. + +NPM provenance is disabled by default. Keep `publish.provenance` omitted or set +to `false` unless the release runtime is explicitly configured so npm can +detect a supported provenance provider from inside Dagger. + +For package-only repositories that do not use Rush Delivery cache metadata, set +`rush-cache-provider: off` or omit the input. `.dagger/rush-cache/providers.yaml` +is only required when `rush-cache-provider: github` is selected. +They can also omit application-image metadata and leave +`application-image-provider: off` unless their deploy selection contains an OCI +target. + +## Runtime Files + +`runtime-file-map` is a multiline list of `SOURCE=>DEST` entries. `SOURCE` is a +file path on the GitHub runner, and `DEST` is a safe relative path inside the +runtime files bundle passed to Dagger. + +Empty `SOURCE` values are skipped. This supports conditional provider auth +steps where an output is intentionally blank for some target selections. + +```yaml +runtime-file-map: | + ${{ steps.auth.outputs.credentials_file_path }}=>gcp-credentials.json +``` + +Use runtime files only for deploy-platform inputs. OCI registry tokens, Cosign +private keys, signing passwords, and Cosign public keys belong in +`workflow-env` or `deploy-env` under the names declared by the selected +application-image provider; Rush Delivery exposes them only to Package. + +Deploy target metadata can mount those files with: + +```yaml +runtime: + env: + GOOGLE_APPLICATION_CREDENTIALS: /runtime-files/gcp-credentials.json + file_mounts: + - source: gcp-credentials.json +``` + +## Raw Dagger Mode + +The action mode does not replace raw Dagger usage. Local runs, other CI +providers, and lower-level debugging can still call the module directly: + +```sh +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 call workflow \ + --git-sha="$GITHUB_SHA" \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN +``` diff --git a/docs-versions/versioned_docs/version-v0.9.0/index.md b/docs-versions/versioned_docs/version-v0.9.0/index.md new file mode 100644 index 0000000..da5adb4 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/index.md @@ -0,0 +1,10 @@ +--- +id: "index" +title: "Docs" +sidebar_label: "Docs" +description: "Documentation for Rush Delivery v0.9.0." +--- + +You are viewing archived documentation for Rush Delivery v0.9.0. + +Choose a page from the sidebar, or start with the [Quick Start](quick-start/github-actions). diff --git a/docs-versions/versioned_docs/version-v0.9.0/introduction.md b/docs-versions/versioned_docs/version-v0.9.0/introduction.md new file mode 100644 index 0000000..e4c628f --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/introduction.md @@ -0,0 +1,71 @@ +--- +id: "introduction" +title: "Introduction" +sidebar_label: "Introduction" +--- + +Rush Delivery is a provider-adaptable Dagger module for Rush monorepos. The +framework assumes Rush is the project graph and uses `.dagger/` metadata as the +extension surface for validation, packaging, deployment, caches, and toolchains. + +## Guides + +- [Quick Start](../quick-start/github-actions): recommended ways to run Rush + Delivery from GitHub Actions, CI scripts, and local working trees. +- [GitHub Action usage](../github-action): GitHub CI wrapper for validation, + deploy release workflows, and npm package release workflows. +- [Public Dagger API](../api): callable functions and when to use them. +- [Entrypoints reference](../entrypoints): every callable Dagger function and + separate-use workflow. +- [Workflow guide](../workflows): local and CI workflow shapes. +- [Metadata contracts](../metadata): files under `.dagger/` that define target + behavior. +- [Provider adapters](../providers): source, registry, cache, and CI-provider + boundaries. +- [Bounded local-copy imports](../local-copy-source-imports): exclude disposable + worktree data before Dagger uploads it, with tested inclusion and recovery. +- [Project-owned Rush toolchain](../rush-toolchain): safely add digest-pinned, + checksummed executables to every Rush lifecycle. +- [Upgrade to v0.9.0](../upgrade-v0-9-0): compatibility, canary, and recovery + guidance for v0.8.1 users. +- [OCI application images tutorial](../tutorial/oci-application-images): + runnable path from provider-off planning through signed publication, + digest-only deploy, split-stage handoff, and rollback. +- [OCI application images](../oci-application-images): build-once image + publication, verified evidence, and digest-only deploy handoff. +- [OCI registry recipes](../oci-registry-recipes): provider metadata, + permissions, retention, and cleanup for common registries. +- [OCI application image troubleshooting](../oci-application-image-troubleshooting): + diagnosis and recovery by release phase. +- [Environment-selected OCI profiles](../tutorial/oci-application-images/environment-profiles): + route one provider definition to staging and production repositories. +- [Mixed Node/Python toolchain](../tutorial/mixed-node-python-toolchain): + install and cache a pinned Python package manager before Rush commands. +- [Development](../development): maintainer checks, website build notes, and + generated documentation inputs. +- [AI architecture](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/.ai/architecture.md): high-level design map for future + coding agents. +- [AI conventions](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/.ai/conventions.md): contribution rules and invariants. + +## Source Of Truth + +The schemas under [`../schemas`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas) are the field-level metadata +contract. These docs explain intent and usage; schemas define file shape. + +Published schemas are available from the documentation site: + +- `https://bootstraplaboratory.github.io/rush-delivery/schemas/.schema.json` +- `https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/.schema.json` + +Use exact versioned schema URLs in project metadata editor hints so older +projects keep the schema contract they were written against. The root +`/schemas/` URLs point at the current release line. + +## Package Release Reference + +The package release docs use +[BootstrapLaboratory/labkit](https://github.com/BootstrapLaboratory/labkit) as a +real npm package publishing reference. LabKit publishes public npm packages with +Rush Delivery `v0.7.0`, Rush change files, `.dagger/release/npm.yaml`, and the +same package release contract that can run standalone or as part of +`workflow`. diff --git a/docs-versions/versioned_docs/version-v0.9.0/local-copy-source-imports.md b/docs-versions/versioned_docs/version-v0.9.0/local-copy-source-imports.md new file mode 100644 index 0000000..97d0728 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/local-copy-source-imports.md @@ -0,0 +1,186 @@ +--- +id: "local-copy-source-imports" +title: "Bounded Local-Copy Imports" +sidebar_label: "Bounded Local-Copy Imports" +description: "Bound local worktree transfer before Dagger uploads source." +--- + +Rush Delivery `v0.9.0` applies local-copy exclusions before Dagger traverses and +uploads the repository. Use the bundled `rush-delivery-local` launcher for +unpushed worktrees and use Git source mode in CI whenever the source already +exists at a remote commit. + +## Install The Versioned Launcher + +The launcher is a release asset and the same byte-for-byte file bundled in the +GitHub Action. It requires Bash 4+, the caller-selected Dagger CLI, and standard +POSIX file tools. It does not require Node.js, `jq`, a project install, or GNU +`realpath`. + +```sh +curl --fail --location \ + --output rush-delivery-local \ + https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.0/rush-delivery-local +printf '%s %s\n' \ + '802ed18dc3bce89974d64884fe3c7ca64f3e206faa4c8c8eef237757101bd391' \ + rush-delivery-local | sha256sum --check --strict +chmod 0755 rush-delivery-local +``` + +Keep the launcher and module on the same release: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=manual \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +The launcher accepts `workflow`, `validate`, and `release-packages`. It owns the +local `repo` and source-mode arguments; passing `--repo`, `--source-mode`, or +Git source coordinates after `--` is rejected. + +## Default Boundary + +The default `bounded` policy sends these ordered exclusions to Dagger's +`host.directory` operation: + +```text +**/node_modules +**/.venv +**/__pycache__ +**/.rush +**/rush-logs +.trunk/out +.trunk/logs +``` + +This is a transfer boundary, not only container cleanup. `.git`, `rush.json`, +and `.dagger` must remain present. The source adapter validates them before +workflow work begins. Git history is retained for affected-project comparison, +deploy-tag lookup, validation, and package release planning. + +Rush Delivery deliberately does not import `.gitignore` as this contract. +Ignored build outputs can be valid Package inputs, while tracked dependencies +can still be disposable for Dagger execution. + +## Repository Extensions + +Create `.dagger/source-import.ignore` only when the defaults need an extension +or an intentional inclusion. The repository also provides a reviewed +[configuration fragment](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/deployment-environment-compatibility/source-import.ignore): + +```text +# Exclude generated browser caches. +apps/web/.cache + +# This generated tool is a required workflow input. +!tools/python/.venv/bin/uv +``` + +Rules are ordered after the defaults. An ordinary line excludes and one +leading `!` re-includes, so the later inclusion wins. Blank lines and lines +beginning with `#` are ignored. UTF-8, LF, CRLF, and a final line without a +newline are supported. + +Patterns must be normalized repository-relative ignore patterns. Absolute or +parent-traversing paths, control characters, unsupported escapes, repeated +`!`, expression characters, and direct removal of `.git`, `.dagger`, or +`rush.json` are rejected. The launcher also quotes accepted repository paths, +ignore-file paths, and patterns as Dagger Shell data. Only `extra-args` remains +an explicitly trusted raw Action/CLI escape hatch. + +If a required generated path is below an excluded directory, include the exact +path and test it before changing production CI. Inclusions do not make arbitrary +split-stage outputs implicit framework contracts. + +## GitHub Action + +Git source mode does not read local-copy settings and emits one fixed diagnostic +that they were ignored. A local-copy Action call uses the bounded policy by +default: + +```yaml +- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + fetch-depth: 0 + +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + source-mode: local_copy + repo: . + source-import-policy: bounded + source-import-ignore-file: .dagger/source-import.ignore + dry-run: "true" +``` + +The Action invokes the bundled launcher parser and passes its generated Shell +to the pinned Dagger Action. Local and Action precedence therefore cannot drift. + +For emergency rollback, `source-import-policy: legacy` selects the released +top-level `dagger call` path and does not read the ignore file. Use it only while +identifying and adding the required inclusion: + +```yaml +with: + source-mode: local_copy + repo: . + source-import-policy: legacy +``` + +The standalone launcher has the equivalent +`--source-import-policy=legacy` flag. It rejects a simultaneous ignore-file +flag because legacy mode cannot apply repository-controlled filters. + +## Entrypoint Data Matrix + +| Entrypoint | Git history | Rush source/config | `.dagger` metadata | Installed dependencies | Generated/package evidence | +| ------------------------ | ---------------------------------- | -------------------- | ------------------ | ------------------------------------------- | ---------------------------------------------------------- | +| `workflow` | Required | Required | Required | Recreated in Dagger | Produced during the composition | +| `validate` | Required | Required | Required | Recreated in Dagger | Not an input | +| `release-packages` | Required | Required | Required | Recreated in Dagger | Not an input | +| `detect` | Required for comparisons/tags | Required | Required | Not required | Not required | +| `build-deploy-targets` | Project-dependent | Required | Required | Recreated in Dagger | Produces build outputs | +| `package-deploy-targets` | Provenance-dependent | Required | Required | Needed only for Rush-requiring package work | Existing build outputs and `.dagger/runtime` may be inputs | +| `deploy-release` | Deploy-tag behavior may require it | Deploy metadata only | Required | Not required | Package manifest, evidence, and runtime files are inputs | + +The launcher wraps only the three source-adapter entrypoints in the first three +rows. Split-stage callers compose `host.directory` themselves and pass that +Directory directly to the required-repo stage. They must include build outputs, +the package manifest, evidence, and `.dagger/runtime` needed by that stage. + +Direct calls to the old top-level `workflow`, `validate`, and +`release-packages` functions remain compatible and retain their v0.8.1 static +filters. Use the launcher when repository-controlled pre-import filtering or a +later inclusion is required; the old decorators intentionally cannot preserve a +caller re-inclusion. + +## Production Verification + +Run once with plain progress and inspect the first source operation: + +```sh +DAGGER_NO_NAG=1 ./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- validate \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=pull_request \ + --pr-base-sha="$(git merge-base HEAD origin/main)" +``` + +The `host.directory` call must contain the defaults followed by the repository +patterns. Confirm the affected-project plan still sees the expected base and +tags. For each inclusion, add a CI assertion that consumes the required output; +mere presence in a local worktree does not prove it crossed the boundary. + +If bounded mode reports a missing mandatory path, restore that path rather than +excluding the validation. If workflow behavior changes only under bounded mode, +switch temporarily to `legacy`, identify the matched required path, add the +narrowest later `!` inclusion, and return to `bounded`. diff --git a/docs-versions/versioned_docs/version-v0.9.0/metadata.md b/docs-versions/versioned_docs/version-v0.9.0/metadata.md new file mode 100644 index 0000000..98adaa4 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/metadata.md @@ -0,0 +1,418 @@ +--- +id: "metadata" +title: "Metadata" +sidebar_label: "Metadata" +--- + +Project-specific behavior lives under `.dagger` in the caller's Rush +repository. This module treats those files as the public extension contract. + +Exact field validation is defined by JSON schemas under +[`../schemas`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas). + +For editor integration in external projects, prefer exact versioned schema +URLs. For example: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-target.schema.json +``` + +The root `https://bootstraplaboratory.github.io/rush-delivery/schemas/` URLs +track the current release. Exact paths such as `/schemas/v0.9.0/...` are the +stable contract for projects pinned to that Rush Delivery version. + +## Package Release + +Package release metadata lives in `.dagger/release/npm.yaml`. It is separate +from deploy target metadata because npm package releases are registry side +effects, not deploy mesh targets. + +Repositories that only use `release-packages` do not need deploy metadata such +as `.dagger/deploy/services-mesh.yaml`. Rush cache metadata is only required +when a Rush cache provider such as `github` is enabled. + +The first supported release strategy uses Rush change files. Rush remains the +source of truth for package selection, version changes, changelogs, and +publishable package rules. + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/npm-release.schema.json + +kind: npm + +versioning: + strategy: rush-change-files + target_branch: main + +auth: + kind: token + token_env: NPM_TOKEN + +publish: + registry: https://registry.npmjs.org/ + tag: latest + access: public + provenance: false +``` + +Fields: + +- `kind`: currently `npm`. +- `versioning.strategy`: currently `rush-change-files`. +- `versioning.target_branch`: branch Rush publishes the generated version + commit back to, usually `main`. +- `auth.kind`: currently `token`. +- `auth.token_env`: release env key containing the npm token, usually + `NPM_TOKEN`. +- `publish.registry`: optional npm registry URL passed to `rush publish`. +- `publish.tag`: npm dist-tag, defaulting to `latest`. +- `publish.access`: optional npm access level, `public` or `restricted`. +- `publish.provenance`: optional boolean, defaulting to `false`. + +For token auth, keep the npm token in the release env file and reference it +from `common/config/rush/.npmrc-publish`, for example: + +```text +//registry.npmjs.org/:_authToken=${NPM_TOKEN} +``` + +The repository still owns Rush and npm package policy. For example, LabKit uses +a Rush version policy: + +```json +[ + { + "definitionName": "individualVersion", + "policyName": "labkit" + } +] +``` + +and each publishable Rush project references it from `rush.json`: + +```json +{ + "packageName": "@omgjs/labkit-webapp-ui", + "projectFolder": "packages/webapp-ui", + "versionPolicyName": "labkit" +} +``` + +Package-level npm metadata remains package-owned: + +```json +{ + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "files": ["dist/**/*", "README.md"] +} +``` + +Private tooling packages can stay in the Rush repo for build and lint support +without becoming part of the public release contract. Rush and npm decide what +is publishable from package metadata and Rush publish behavior; Rush Delivery +does not maintain a separate package allowlist. + +`publish.provenance` defaults to `false`. Keep it omitted or set to `false` +for the default Dagger-contained release flow, because npm automatic provenance +needs to detect a supported CI/OIDC provider from inside the publishing +environment. Set `publish.provenance: true` only when that release runtime is +explicitly wired for a supported npm provenance provider. + +Pull-request validation runs Rush change-file verification when npm release +metadata is present. Live `releasePackages` runs the shared Rush lifecycle in +build-first order (`build`, `lint`, `test`, `verify`), lets Rush apply the +change files, publishes packages, and pushes the generated version commit back +to `versioning.target_branch`. In Git source mode, Rush Delivery prepares that +target branch locally before invoking `rush publish` so Rush can check it out +for the final merge. + +Schema: +[`../schemas/npm-release.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/npm-release.schema.json) + +## Deploy Services Mesh + +`.dagger/deploy/services-mesh.yaml` defines deploy target ordering: + +- `services..deploy_after` lists targets that must finish first. +- Targets with no dependency can run in the same deploy wave. +- Service names must match deploy target metadata names. + +Schema: +[`../schemas/deploy-services-mesh.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/deploy-services-mesh.schema.json) + +## Deploy Targets + +Deploy targets live in `.dagger/deploy/targets`. + +Each target declares: + +- `name`: target name. It should match the metadata filename and Rush package. +- `deploy_script`: repository-relative script executed by the target runtime. +- `runtime.image`: base image for the executor container. +- `runtime.install`: toolchain preparation commands. +- `runtime.pass_env`: allowed 1:1 host-to-container environment variables. +- `runtime.map_env`: allowed renamed environment variables, written as + `TARGET_ENV: SOURCE_ENV`. +- `runtime.env`: static container environment values. +- `runtime.dry_run_defaults`: safe defaults used during dry-runs. +- `runtime.required_host_env`: host environment keys required for live runs. +- `runtime.file_mounts`: deploy-platform files mounted into the runtime + container from the runtime files bundle, or from host env paths for + compatibility. +- `runtime.workspace`: directories and files mounted under `/workspace`. + +If `runtime.workspace.mode` is `full`, the whole prepared repository is mounted. +If mode is omitted, only listed `dirs` and `files` are mounted. + +The framework-owned `.dagger/runtime/evidence` subtree is excluded from both +workspace modes. For a published OCI target, Rush Delivery validates that +target's evidence and mounts only its directory at the framework-owned +`ARTIFACT_EVIDENCE_DIR`. Deploy scripts must read evidence from that variable, +not request the internal subtree as workspace metadata. + +Runtime file mounts use a `source` path relative to the `runtimeFiles` bundle. +`target` is optional and defaults to `/runtime-files/`. + +```yaml +runtime: + env: + GOOGLE_APPLICATION_CREDENTIALS: /runtime-files/gcp-credentials.json + file_mounts: + - source: gcp-credentials.json +``` + +The `source` path must stay inside the runtime files bundle: no absolute paths +and no `..` segments. Live deploys that reference `source` mounts require the +`runtimeFiles` Dagger input. Dry-runs report the intended mount and do not +require the file. + +Compatibility mounts can still read a host path from an allowlisted environment +variable and mount it at an explicit target: + +```yaml +runtime: + required_host_env: + - GOOGLE_GHA_CREDS_PATH + file_mounts: + - source_var: GOOGLE_GHA_CREDS_PATH + target: /tmp/gcp-credentials.json +``` + +For renamed deploy env with `runtime.map_env`, `runtime.dry_run_defaults` are +keyed by the source variable name. + +`runtime.pass_env`, `runtime.map_env`, and static `runtime.env` share one output +environment namespace and have no precedence order. If they resolve the same +output name with different values, Rush Delivery fails instead of silently +overriding one value with another. + +`ARTIFACT_*`, `GIT_SHA`, and `DRY_RUN` are framework-owned runtime names. +Deploy target metadata cannot project or define them. Exact field constraints +remain in the schema linked below. + +Schema: +[`../schemas/deploy-target.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/deploy-target.schema.json) + +## Package Targets + +Package targets live in `.dagger/package/targets`. + +Package targets can also declare build-time environment for the generic Rush +`verify`, `lint`, `test`, and `build` stage: + +- `build.pass_env`: allowed 1:1 variables from the deploy env file. +- `build.map_env`: allowed renamed variables, written as + `TARGET_ENV: SOURCE_ENV`. +- `build.dry_run_defaults`: safe values used when workflow dry-run mode is + enabled and a source variable is not present. + +```yaml +build: + pass_env: + - WEBAPP_URL + map_env: + VITE_GRAPHQL_HTTP: WEBAPP_VITE_GRAPHQL_HTTP + dry_run_defaults: + WEBAPP_URL: https://webapp.example.test + WEBAPP_VITE_GRAPHQL_HTTP: https://api.example.test/graphql +``` + +Rush Delivery merges build env from all selected package targets into the +shared Rush build container. If two selected targets resolve the same target +environment variable to different values, the build fails with a metadata error. +For `map_env`, `dry_run_defaults` are keyed by the source variable name. + +`build.pass_env` and `build.map_env` also have no precedence order. Both add +explicit build environment variables. If they resolve the same output name with +different values, Rush Delivery fails instead of silently overriding one value +with another. + +Supported artifact types: + +- `directory`: an already-built repository directory. +- `rush_deploy_archive`: a Rush deploy output packaged for a deploy target. +- `oci_image`: a single-platform application image built, scanned, published, + signed, and handed to Deploy by immutable digest. + +An OCI artifact declares a repository-relative build `context`, a Dockerfile +inside that context, a relative image name, one explicit `platform`, and a +scanner policy: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +name: control-plane-api + +artifact: + kind: oci_image + context: . + dockerfile: deploy/images/control-plane-api.Dockerfile + image: control-plane-api + platform: linux/amd64 + scan: + fail_on: [high, critical] + ignore_file: .dagger/application-images/grype.yaml +``` + +OCI targets require a full source revision for packaging. Existing +directory/archive-only projects do not require this artifact shape, provider +metadata, or registry credentials and retain their legacy manifest output. + +Schema: +[`../schemas/package-target.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/package-target.schema.json) + +## Application Image Providers + +OCI registry and signing provider metadata lives at +`.dagger/application-images/providers.yaml`. Provider names are selected by the +`applicationImageProvider` API input; `off` is reserved and remains the default. + +Illustrative provider metadata (replace the example registry and namespace with +an accepted registry recipe): + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + release: + kind: oci_registry + registry: registry.example.com + repository_prefix: product/images + username_env: OCI_USERNAME + token_env: OCI_TOKEN + signing_key_env: OCI_SIGNING_KEY + signing_password_env: OCI_SIGNING_PASSWORD + verification_key_env: OCI_SIGNING_PUBLIC_KEY +``` + +Only environment variable names belong in metadata. Every one of the five names +must be globally unique across all declared providers. Selected values come +from the workflow-plus-deploy environment overlay during live Package. Token, +private key, password, public key, and derived Docker configuration become +Dagger secrets; the registry username is a required non-secret Dagger auth +input. None reach the image build or Deploy runtime. Multiline Cosign PEM values +may use literal `\n` separators in flat env files. + +Registry coordinates may remain static as above or use one public environment +name per role: + +```yaml +providers: + release: + kind: oci_registry + registry_env: APP_IMAGE_REGISTRY + repository_prefix_env: APP_IMAGE_REPOSITORY_PREFIX + username_env: OCI_USERNAME + token_env: OCI_TOKEN + signing_key_env: OCI_SIGNING_KEY + signing_password_env: OCI_SIGNING_PASSWORD + verification_key_env: OCI_SIGNING_PUBLIC_KEY +``` + +Exactly one of `registry`/`registry_env` and exactly one of +`repository_prefix`/`repository_prefix_env` is required. Mixed static/dynamic +definitions are valid. Coordinate values are public Package routing inputs, +not credentials. Their names must be distinct from one another and every +repository/invocation credential capability. Workflow resolves them from the +workflow-plus-deploy overlay; standalone Package entrypoints use +`deployEnvFile`. Deploy never reloads them. Follow the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles). + +Every credential name declared by every application-image provider is reserved +from package build and deploy environment projections. This is a cross-file +rule, so the metadata contract enforces it after schema validation rather than +duplicating provider-specific names in static JSON Schema. Do not put registry +tokens or Cosign key material in the deploy runtime files bundle. + +Schema: +[`../schemas/application-image-providers.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/application-image-providers.schema.json) + +## Package Manifest + +Directory/archive-only selections keep the existing unversioned manifest. Any +selection containing an OCI artifact emits the strict +`rush-delivery-package-manifest/v2` envelope. Published OCI artifacts require a +canonical digest reference, full source revision, one platform, and verified +SBOM, scan, provenance, and signature evidence. Deploy accepts both manifest +contracts. + +Schema: +[`../schemas/package-manifest.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/package-manifest.schema.json) + +For the complete package and deploy flow, follow the +[OCI application images tutorial](../tutorial/oci-application-images), +then use the [production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting). + +## Validation Targets + +Validation targets live in `.dagger/validate/targets`. + +They declare optional backing services and ordered validation steps. This keeps +target-specific smoke checks in metadata while the runner stays generic. + +Schema: +[`../schemas/validation-target.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/validation-target.schema.json) + +## Toolchain Images + +Toolchain image provider metadata lives in +`.dagger/toolchain-images/providers.yaml`. + +It declares optional registry providers for reusable framework toolchain images. +Provider `off` needs no metadata. Provider `github` uses GHCR with environment +keys for repository, username, and token. + +Schema: +[`../schemas/toolchain-image-providers.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/toolchain-image-providers.schema.json) + +Project-owned Rush tool metadata is separate and optional at +`.dagger/toolchains/rush.yaml`. It selects a digest-pinned Node 24 Debian base +and 1–16 ordered, SHA-256-verified HTTPS downloads installed as fixed +executables under `/usr/local/bin`. Unknown fields and generic commands are +rejected. Absence preserves the default Rush toolchain spec and cache identity. + +Schema: +[`../schemas/rush-toolchain.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/rush-toolchain.schema.json) + +Use the [project-owned toolchain guide](../rush-toolchain) and +[mixed Node/Python tutorial](../tutorial/mixed-node-python-toolchain) rather +than duplicating schema restrictions in project scripts. + +## Rush Cache + +Rush cache metadata lives in `.dagger/rush-cache/providers.yaml`. + +The `cache` section defines: + +- `version`: user-controlled cache snapshot tag. Bump it when you intentionally + want to start a fresh Rush install cache namespace. +- `paths`: repository-relative Rush install cache paths restored into the + Dagger-owned source. + +The `providers` section declares optional storage adapters. + +Schema: +[`../schemas/rush-cache-providers.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/rush-cache-providers.schema.json) diff --git a/docs-versions/versioned_docs/version-v0.9.0/oci-application-image-troubleshooting.md b/docs-versions/versioned_docs/version-v0.9.0/oci-application-image-troubleshooting.md new file mode 100644 index 0000000..cd647b3 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/oci-application-image-troubleshooting.md @@ -0,0 +1,540 @@ +--- +id: "oci-application-image-troubleshooting" +title: "OCI Application Image Troubleshooting" +sidebar_label: "OCI Application Image Troubleshooting" +description: "Diagnose OCI release failures safely by phase." +--- + +Use this runbook for Rush Delivery `v0.9.0` OCI Package and digest-only Deploy +failures. The central rule is simple: once registry mutation may have started, +do not automatically replay the whole workflow. Inspect the subject, navigation +tag, signatures, and attestations first. + +For the underlying contract, read +[OCI application images](../oci-application-images). Provider-specific +inspection and cleanup links are in +[Registry recipes](../oci-registry-recipes). + +## First Response + +1. Stop automatic retries and preserve the first sanitized failure. +2. Record the entrypoint, dry/live mode, selected target names, provider name, + full source SHA, registry authority/repository, and reported failure stage. +3. Decide whether the failure is before or after the registry mutation boundary + using the matrix below. If uncertain, classify it as partial/unknown. +4. Never print or attach an env file, registry token, username sentinel, private + key, public key, signing password, generated Docker config, Dagger secret, or + an unreviewed debug/trace export. +5. If mutation is possible, inspect the unique repository namespace, attachment + tags, and every tagged or untagged package version before a manual retry. + Keep or remove objects according to the provider's release and cleanup + policy. +6. If Deploy may have started, inspect the deployment platform separately. A + retained registry digest does not prove whether a platform rollout occurred. + +## Error Matrix + +| Observed error or symptom | Likely stage | Safe first diagnostic | Side effect and action | +| ---------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| A named global provider appears to do nothing | Selection/activation | Confirm the selected CI plan contains no `oci_image` artifact. | Intended: no provider metadata or provider credential entry is resolved/used and no OCI side effect occurs. A supplied aggregate env file may still be parsed for other capabilities. | +| `applicationImageProvider` is required for live OCI packaging | Pre-Build activation | Confirm the selected target's package artifact is `oci_image` and the provider option is not `off`. | No application-image mutation. Select a valid provider and retry. | +| Provider file missing | Named-provider activation | Confirm `.dagger/application-images/providers.yaml` exists in the source revision actually passed to Dagger. | No mutation. Restore the file and retry. Filesystem-only selection does not need it. | +| Unknown/invalid provider | Named-provider activation | Compare the literal option with provider mapping keys; metadata does not interpolate variables. | No mutation. Correct the name/metadata and retry. | +| Provider requires host env `NAME` | Live credential resolution | Check in the secret manager/job mapping that the named value exists and is non-empty. Do not print it. | No mutation. Correct mapping and retry. | +| Credential projection validation failed | Cross-file capability validation | Read the provider, target, field, and environment **name** in the aggregate error. | No mutation. Rename the project projection or use a dedicated provider name; retry. | +| Framework-owned environment variable is rejected | Deploy metadata parser/runtime | Search the named target field for `ARTIFACT_*`, `GIT_SHA`, or `DRY_RUN`. | No new mutation. Rename the project-owned variable; never shadow framework identity. | +| Cosign preflight toolchain is unavailable before key preflight | Secret-free tool availability | Check reachability of the pinned Cosign and BusyBox helper images, DNS, trusted TLS, and Dagger cache/registry status. Do not rotate or print keys. | No destination mutation. Restore both tool images' availability and retry. | +| Cosign preflight failed for signing private key | Offline key preflight | Confirm the secret is the encrypted Cosign private-key file encoded with literal `\n`. | No destination mutation. Recreate/re-encode key and retry. | +| Cosign preflight failed for signing password | Offline key preflight | Check secret version and key/password rotation transaction without exposing either value. | No destination mutation. Correct the pair and retry. | +| Cosign preflight failed for verification key | Offline key preflight | Confirm the configured value is the public key from the selected key pair, literal-`\n` encoded. | No destination mutation. Correct and retry. | +| Cosign preflight failed for signing/verification key pair | Offline key preflight | Compare secret-manager version IDs/fingerprints from protected inventory, not PEM text. | No destination mutation. Activate a matching pair and retry. | +| Docker image build failed | OCI preparation | Check context, Dockerfile containment, prior Rush build output, and the selected platform. | No selected OCI target was published. Fix and retry the batch. | +| Syft generation or SPDX validation failed | OCI preparation | Record Syft image/version and sanitized stage error; verify the exact built subject can be exported. | No publication. Fix tool/network/subject issue and retry. | +| Grype database download/cache/freshness failed | OCI preparation | Record Grype version, database build/status metadata, cache availability, and outbound reachability without dumping cache content. | No publication. Restore governed database availability/freshness and retry. | +| Grype produced invalid scanner output | OCI preparation | Check whether `matches` is an array and whether each evaluated item has a non-empty ID and supported severity. | No publication. Treat as tool/output failure, not “zero findings.” | +| Vulnerability policy rejected findings | OCI preparation | Review IDs/severities in the sanitized error and full report under security access controls. | No publication. Remediate or add a governed narrow Grype rule, then retry. | +| Registry readiness/transport failure before Package mutation | Harness/infrastructure | Probe trusted `https:///v2/` with bounded timeouts; `200`, `401`, or `403` proves service reachability, not write auth. | Safe to retry only the bounded probe/read. Do not turn it into an automatic Package retry. | +| `failed during registry publication authentication` | Publication | Check token lifetime, username form, credential type, and clock using the provider control plane. | The fixed stage describes the registry response, not proof of zero side effects. Inspect the target repository before retry unless provider audit evidence proves rejection before every upload. | +| `failed during registry publication authorization` | Publication | Check repository existence plus subject and digest-derived Cosign attachment push/read scopes for the resolved identity. | Blob or manifest work may already have started. Inspect the target repository and all package versions before retry. | +| `failed during registry publication transport` | Publication | Check bounded DNS, trusted-TLS, connection, timeout, and provider-status evidence without replaying Package. | Outcome is unknown/partial because the publication boundary was crossed. Inspect the target repository before retry. | +| Generic `failed during registry publication` | Publication | Use provider audit events and a complete tagged/untagged package-version inventory; do not infer a credential or network cause from the generic stage. | Outcome is unknown/partial. Inspect and clean or quarantine the disposable namespace before a controlled retry. | +| Repository not found/permission denied | Publication | Confirm every literal repository path exists and the identity has subject plus Cosign-object push/read permission. | Usually pre-manifest, but blob uploads may have started. Inspect the repository before retry. | +| TLS, unknown authority, or insecure registry error | Tool pull/publication/Cosign | Confirm the endpoint presents a publicly trusted chain from the Dagger/Cosign execution environment. | No supported custom-CA or insecure bypass exists. Move to trusted TLS; inspect if upload may have begun. | +| Returned publication reference is malformed/unexpected | Post-publish reference validation | Record expected repository, SHA tag, and sanitized returned authority/name/digest shape. | Publication occurred or may have occurred. Inspect digest/tag; do not sign or deploy it manually as a workaround. | +| Cosign sign/attest/verify failed | Ordered finalization | Use the canonical subject reference in the sanitized error to inventory attachment tags, all tagged/untagged package versions, and key inventory. | Subject and some Cosign objects may exist. No successful manifest or Deploy; clean/quarantine before retry. | +| Evidence finalization failed after Cosign verification | Ordered finalization | Record the canonical subject, target, evidence kind/path, and sanitized Dagger stage without attaching unreviewed evidence contents. | Subject, signature, and both attestations may all exist, but no successful manifest or Deploy exists. Inspect and clean/quarantine before retry. | +| Earlier published target / later target was not started | Multi-target finalization | Follow each canonical earlier/failed reference in stable error order. | Earlier siblings are external side effects; later listed targets were not started. Inspect each repository independently. | +| Transport interruption during/after publication | Publication/finalization | Check provider audit logs, SHA tag, subject digest, attachment tags, and all package versions. | Outcome is unknown/partial. Never assume the first attempt did nothing and never auto-replay the batch. | +| Live Deploy requires a published OCI artifact | Deploy preflight | Inspect manifest `status`; a provider-off or named dry-run manifest is `planned`. | No Deploy target starts. Produce a new live Package bundle; do not edit status. | +| Frozen credential capability missing or invalid during standalone OCI Deploy | Deploy credential-boundary activation | A `v0.9.0` bundle must contain a valid `.dagger/runtime/application-image-credential-capability.json`; only an older bundle without that file uses `.dagger/application-images/providers.yaml` as a legacy names-only fallback. A present malformed capability always fails closed. | No Deploy target starts and no provider credential value is read. Restore the intact trusted bundle; do not delete the capability or `repository` field to force a weaker path. | +| Provider credential projection rejected during standalone Deploy | Deploy credential-boundary activation | Inspect the named Deploy target/field and the named credential declaration. The check includes every declared provider, not only the provider that originally published the artifact. | No Deploy target starts and no registry operation occurs. Rename and separately scope the project-owned capability; rebuild the trusted bundle after a metadata change. | +| OCI source revision does not match deploy `gitSha` | Deploy preflight | Compare manifest SHA with protected release metadata outside the bundle. | No Deploy target starts. Restore the correct bundle/SHA pair; do not override or truncate SHA. | +| Reference must equal repository@digest | Manifest parser/preflight | Validate the manifest came intact from Package and was not rewritten by CI templating. | No Deploy target starts. Restore the original bundle; do not repair fields by hand. | +| Evidence file missing/unreadable or hash mismatch | Deploy evidence preflight | Confirm the whole packaged directory was restored atomically and compare its external archive checksum. | No live wave starts. Restore the trusted archive; do not copy one evidence file from another run. | +| `.dagger`, `.dagger/runtime`, or `.dagger/runtime/evidence` is a symlink | Common Deploy bundle preflight | Inspect those exact paths without dereferencing them and compare the complete archive with its protected identity/checksum. | No dry or live target starts. Do not patch/repack the bundle; rerun the `v0.9.0` Package producer, export its complete result, and register a new archive and protected release record. | +| Deploy script cannot see another target's evidence | Workspace assembly | Confirm it is not depending on `.dagger/runtime/evidence/`. | Intended isolation. Consume only current `ARTIFACT_EVIDENCE_DIR`. | +| Deployment platform cannot pull the digest | Project Deploy/platform | Confirm platform pull identity, network, repository read scope, and retained digest. | Package already succeeded; a platform rollout may have failed. Fix pull identity and retry Deploy with the same digest. | +| Docker socket missing | Project Deploy compatibility | Determine whether the project deploy script invokes Docker; first-class OCI Package does not. | For OCI-only jobs keep the socket disabled. Enable it only for a trusted legacy deploy script that requires host-level daemon authority; never expose it to untrusted checkout code. | +| Retained bundle is valid but registry digest is unavailable | Rollback/platform pull | Check registry retention/audit records and exact digest, not the SHA tag. | The bundle cannot recreate a deleted subject. Recover from an independently retained registry copy or choose another trusted release. | + +## Selection And Provider Problems + +### No OCI selected with a named global input + +This is not an error in `v0.9.0`. Selection determines activation. A +directory-only, archive-only, empty, or npm-only execution ignores the unused +application provider, provider file, and credentials. This permits an existing +filesystem project to upgrade without adding `.dagger` OCI configuration. + +If OCI was expected, inspect the CI plan and selected package metadata. Do not +“fix” the behavior by making provider parsing unconditional. + +### Provider `off` in live OCI + +`off` is valid for OCI dry-run planning and is the default. It is invalid when a +selected OCI artifact is live. Select a provider only after the target and +provider metadata exist. The failure occurs before Rush Build and destination +registry activity; Source acquisition may already have used its own configured +network/credentials. + +### Missing or unknown provider + +Check all three literal values: + +- selected option, for example `--application-image-provider=ghcr`; +- mapping key under `providers:`; and +- provider metadata path exactly + `.dagger/application-images/providers.yaml`. + +The provider option does not select by `kind`, registry name, environment, or +CI variable. Static `repository_prefix` and `registry` do not interpolate shell +syntax; use their explicit `_env` alternatives for public deployment routing. +Run a named-provider dry run after correction; it validates the planned +repository without resolving provider credentials. Supply only required public +coordinates to that diagnostic. + +### Repository validation differs from execution + +Invocation-scoped execution skips provider parsing until a selected OCI plan is +known. `validate-metadata-contract` intentionally validates every provider file +that is present and every cross-file credential projection. Therefore a +filesystem execution can succeed while repository lint correctly reports an +invalid unused provider file. Fix repository-lint failures rather than treating +the execution path as equivalent validation. + +Standalone Deploy has a narrower, manifest-driven exception. After its initial +manifest/source preflight succeeds, a selected published OCI artifact, or a +selected planned OCI artifact with `repository` in a dry run, causes Deploy to +use the names-only credential capability frozen by Package and reject any +selected Deploy runtime projection of credentials declared by any provider +before a composed Build. An older bundle without that internal handoff falls +back to provider metadata. Deploy never resolves those credential values and +performs no registry or Cosign operation. Filesystem artifacts and provider-off +planned OCI artifacts do not activate this boundary. If a mixed selection +activates it, every selected Deploy target is checked. + +### Missing or malformed public coordinates + +A provider must define exactly one of `registry`/`registry_env` and exactly one +of `repository_prefix`/`repository_prefix_env`. Repository validation can check +that XOR and static syntax without an env file. A named invocation additionally +requires each selected public env value to be present and normalized. + +Errors intentionally report provider, coordinate role, and environment name but +not the invalid raw value. Check the selected workflow/deploy profile directly: + +- registry is an authority without scheme, userinfo, or path; +- repository prefix is a lowercase normalized OCI path without tag/digest; +- workflow/deploy duplicate keys are either absent or exactly equal; and +- coordinate names do not alias another coordinate, credential capability, + `GIT_SHA`, `DRY_RUN`, or `ARTIFACT_*`. + +Use a coordinate-only named dry run to diagnose routing. It reads no selected +provider credential value. Standalone Package reads coordinates only from its +deploy env file; Deploy never reads them. + +## Credential And Key Problems + +### Missing name versus missing value + +Provider YAML contains five environment **names**. A live Package then requires +five non-empty values for the selected provider. A named dry run with no +aggregate env file reads none of them. If a shared env file is supplied for +another capability, its bytes are parsed as a whole, but provider credential +entries are not looked up, used, or converted into Dagger secrets. Confirm +mappings through the CI/secret-manager UI or presence-only checks; +do not run `env`, `set`, `printenv`, `cat `, shell tracing, or a command +that expands the secret into its arguments. + +All providers in an active provider file contribute protected names, while only +the selected provider's values are read. If switching selected providers makes +a collision appear/disappear, the metadata is not following the contract. + +### Actual newlines versus literal `\n` + +Rush Delivery's public env file is one logical `NAME=value` per line. PEM values +must contain literal backslash-plus-`n` separators. An actual newline splits the +record and can produce a missing-name error, invalid env-line error, or malformed +key. + +Malformed-record diagnostics identify the physical line number and redact its +contents. If a diagnostic from another wrapper contains the raw PEM body or +value, treat that wrapper output as sensitive, stop sharing it, and fix its +redaction before continuing. + +Regenerate the flat value from the protected key file using the tested procedure +in [Registry and Cosign bootstrap](../tutorial/oci-application-images/registry-and-cosign-bootstrap), +perform its in-memory round-trip check, then update the secret manager. Do not +diagnose this by printing the encoded or decoded key. Raw multiline PEM is an +internal normalization capability, not a supported multiline env-file record. + +### PEM markers are not cryptographic proof + +The encrypted private key should have the Cosign encrypted-private-key markers, +and the public key should have public-key markers. Marker checks only provide a +format diagnostic. The live preflight is authoritative because it decrypts the +key, derives its public identity, signs a challenge, and verifies the challenge +with both derived and configured keys. + +For wrong-password or mismatch errors, compare secret-manager version IDs and a +separately governed fingerprint inventory. Rotate the private key, password, and +active public key as one controlled transaction. Do not put OCI signing +material in `runtime-file-map`: that bundle is for deployment-platform files and +is visible to project Deploy code. + +### Protected-name collision + +The aggregate error names every provider/credential/target/field collision. +Rename either the provider capability or the project-owned variable. The check +covers: + +- package Build pass-through names, both sides of mappings, and dry defaults; +- Deploy pass-through names, both sides of mappings, static env, dry defaults, + required host names, and host-path mount source variables; and +- npm `auth.token_env` in a composed workflow with active OCI. + +Do not alias the same secret under a new project-visible name. Name-based +validation cannot detect value identity; use separate least-privilege +credentials. + +## Registry And Network Problems + +### Readiness is not authorization + +A `GET https:///v2/` response of `200`, `401`, or `403` shows that a +trusted-TLS registry service responded. It does not prove repository existence, +push scope, returned-reference behavior, or Cosign compatibility. Retry this +side-effect-free probe only with bounded attempts and timeouts. + +Classify DNS failure, connection reset/refusal, timeout, TLS handshake timeout, +and unexpected EOF before mutation as registry transport. Once publish may have +started, the same symptoms mean `registry-transport-ambiguous`. + +### Authentication and repository authorization + +Rush Delivery emits four fixed, secret-safe publication stages. They are +operational classifications, not verbatim registry errors: + +- `registry publication authentication` identifies a credential-identity or + credential-validity denial, such as an explicit `401 Unauthorized`; +- `registry publication authorization` identifies a repository/scope denial, + such as an explicit `403 Forbidden` or `insufficient_scope`; +- `registry publication transport` identifies DNS, connection, trusted-TLS, or + bounded-request availability failure; and +- `registry publication` is the fail-closed fallback for every unrecognized + registry error. + +Rush Delivery discards the original registry exception because clients can put +tokens, signed URLs, or other protected values in it. Do not weaken this +boundary by enabling trace output or wrapping the publisher to print the raw +exception. The fixed stage also cannot prove that a failed target is empty: the +ordered publication boundary is crossed before each registry result is known, +and a registry can accept blobs before rejecting a later request. Treat all +four stages as possibly mutating unless independent provider audit and complete +repository/package-version inventory prove otherwise. + +Use the provider-specific username/token form and check expiry: + +- GHCR: job `GITHUB_TOKEN` with `packages: write`, or a dedicated classic PAT; +- GAR: username `oauth2accesstoken` with a freshly minted short-lived token, + or a deliberately accepted service-account-key fallback; +- ECR: username `AWS` with `get-login-password` output from the correct region; +- Docker Hub: Docker ID/organization name with a dedicated PAT/OAT. + +The publisher needs write plus verification read for the subject and Cosign +objects. The deployment platform needs a separate pull identity. See +[Registry recipes](../oci-registry-recipes) for current official links. + +An authentication denial independently proven to occur before every upload can +be retried after credentials are fixed. A fixed Rush Delivery authentication or +authorization stage alone is not that proof. Inspect the registry first when an +upload/manifest request may have begun or inventory completeness is uncertain. + +### Trusted TLS and custom CA + +`v0.9.0` exposes no custom-CA, self-signed-certificate, plain-HTTP, or +insecure-registry option for application-image providers. Do not add a CLI flag +to bypass TLS verification around Rush Delivery. Use a registry endpoint whose +certificate chain is trusted by both the Dagger engine and pinned Cosign image, +or re-plan custom trust as a separate public contract. + +### Malformed returned reference + +Rush Delivery asks Dagger to publish +`:sha-` and accepts only a returned digest for the +expected repository/tag. It canonicalizes Deploy identity to +`@sha256:<64 lowercase hex>`. + +A malformed or rewritten return happens after the publish call. Record the +expected repository and SHA tag, inspect provider audit logs and digest listings, +and treat the namespace as mutated. Do not manually manufacture a manifest or +continue signing a different reference. + +### Cosign legacy-attachment incompatibility + +The live path must create one subject signature and two attestations, then read +and verify all three. Rush Delivery pins `--new-bundle-format=false` on all six +registry Cosign commands. With Cosign `3.1.2`, that means a digest-derived +`.sig` attachment and a shared `.att` attachment containing both predicates; +the OCI 1.1 Referrers API is not used. A registry can accept the image and still +reject an attachment manifest, tag update, or read. Preserve the canonical +subject, exact fixed Cosign stage, provider service/tier/region, and sanitized +registry error. Compare the exact endpoint with the provider and +[Cosign registry support](https://github.com/sigstore/cosign#registry-support) +documentation. + +One subject plus at least two non-subject package versions is the inventory +lower bound after success. Extra untagged history may remain when the second +attestation replaces the `.att` tag. Counts never prove semantic completeness: +the successful Package must independently verify the signature, SPDX +attestation, and provenance attestation. + +If Package reports a sign, attestation, or verification failure, no successful +manifest is written and Deploy does not start, but registry objects may remain. +Quarantine or clean them before a controlled retry. + +## Scanner Problems + +### Database download, cache, and freshness + +The Grype container is digest-pinned, while the vulnerability database is a +mutable cached/network input. Check: + +- outbound access to the configured database service; +- cache availability and permissions for the Dagger cache volume; +- database build time and age status; +- timeout/rate-limit/proxy diagnostics; and +- whether a previous valid cache exists for the pinned Grype version. + +Anchore documents automatic updates and stale-database failure in +[Vulnerability Database](https://oss.anchore.com/docs/guides/vulnerability/database/). +Do not disable age validation or update checks as an incident shortcut without +an approved security decision. A later run can find different vulnerabilities +because upstream feeds changed; record database identity/time with release +evidence when reproducibility matters. + +### Policy rejection + +`fail_on` is exact-set matching. If policy is `[high]`, a Critical finding alone +does not cause the policy rejection. List both `high` and `critical` for the +usual production policy. + +Review the complete scan evidence only in an access-controlled location. If an +exception is justified, edit the repository-owned Grype config with supported +fields, a narrow vulnerability/package scope, owner, reason, review/expiry date, +and removal follow-up. Anchore documents supported rules and JSON suppression +behavior in [Filter scan results](https://oss.anchore.com/docs/guides/vulnerability/filter-results/). +Do not create an undocumented Rush Delivery-specific ignore list. + +### Changed findings between runs + +Compare source SHA, Docker build input, pinned Grype image, database build/status, +and ignore config. The executable pin does not pin the vulnerability database. +A changed finding is not evidence of application-image registry mutation; +scanning is in preparation, before the publication barrier. + +## Partial Publication And Multi-Target Recovery + +Preparation is all-or-nothing with respect to application-image publication: +every selected filesystem artifact and every OCI Docker build/SBOM/scan must +succeed first. Finalization is intentionally sequential and nontransactional. + +On a finalization error, parse the sanitized report into three sets: + +- **Earlier published:** each named target/reference completed finalization. + Preserve or clean it deliberately. +- **Failed target:** if a canonical digest reference is shown, publication + succeeded and signing/evidence work failed later. If no reference is shown, + publication still may have started. +- **Later not started:** no finalization was invoked for these targets. + +For every earlier/failed repository: + +1. Query the provider control plane/audit log for the deterministic + `sha-` tag and recent manifests. +2. Record each canonical subject digest found. +3. Discover the digest-derived signature/attestation tags and every tagged or + untagged related package version for each subject. +4. Compare inventory with the failed Cosign stage; do not infer completeness + only from object count. +5. Decide to retain/quarantine or delete under provider policy. Use a separate + cleanup identity and the provider links in + [Registry recipes](../oci-registry-recipes). +6. Verify the result of cleanup. Only then authorize a manual rerun of the full + Package flow. + +There is no successful package manifest for the failed attempt. Never synthesize +one from registry state, because Package did not complete its verification and +evidence contract. + +## Manifest, Bundle, And Evidence Problems + +### Planned manifest used live + +A dry run produces `status: planned`; provider `off` omits the repository and a +named provider may include it. Neither is deployable live. Produce a new live +Package bundle. Changing `planned` to `published` cannot create a digest, +evidence, signature, or attestation and is always invalid. + +### Source mismatch + +Deploy compares every selected OCI artifact's `source_revision` with the full +lowercase `gitSha` supplied by the caller. In a split stage, obtain the expected +SHA from protected release metadata outside the unsigned bundle. A mismatch +usually means the wrong bundle was restored, the wrong release record was +selected, or a truncated/mutable ref was supplied. Restore the correct pair; do +not edit either value. + +### Repository/reference/digest disagreement + +A published reference must be exactly `repository@digest`, with a lowercase +`sha256` digest. Mutable tags are rejected. A disagreement indicates a damaged, +rewritten, or adversarial manifest. Verify the external archive checksum and +restore the whole known-good bundle atomically. + +### Missing or changed evidence + +The manifest paths must stay below +`.dagger/runtime/evidence//`, and the provenance, SPDX, and scan +bytes must match their recorded SHA-256 values. Evidence validation for every +selected published target runs before the first live Deploy wave. + +Do not patch the individual file or digest. Verify the archive checksum from +protected metadata, reject archive member/link escapes, and atomically restore +the complete bundle into a new directory. If the external checksum also differs, +obtain the correct immutable CI artifact. + +### Unsigned bundle limitation + +Local hashing detects accidental or isolated modification. It does not detect an +attacker who can replace the unsigned manifest and evidence together with a +schema-valid coordinated bundle. The operator must protect producer and consumer +jobs, store the bundle immutably/access-controlled, record its checksum or +artifact identity externally, and supply an independent full Git SHA. Signed +portable manifests and Deploy-time Cosign verification are not `v0.9.0` +features. + +### Wrong bundle or unavailable digest during rollback + +Verify the older archive against that release's external checksum **before** +extraction, then compare its manifest SHA to the independently stored release +SHA. Use its digest unchanged. If the registry no longer retains the subject or +the platform can no longer pull it, the local bundle cannot recreate it. Choose +another retained trusted release or restore the registry object from a separately +governed registry backup. + +## Deploy And Docker-Socket Problems + +OCI Package uses Dagger-native Dockerfile build and registry APIs. It does not +need `/var/run/docker.sock`, a host Docker/Podman CLI, or a daemon supplied to +the module. The Dagger CLI may itself use a configured container runtime to run +the Dagger engine; that infrastructure relationship is separate from forwarding +a socket to project Deploy code. + +The GitHub Action retains a non-empty Docker-socket default only for existing +project-owned deploy scripts that execute Docker. For OCI-only jobs set: + +```yaml +with: + docker-socket: "" +``` + +If a legacy script genuinely requires Docker, keep the compatibility input only +in the trusted job that runs that script and treat socket access as privileged. +Never enable it to fix registry publication or Cosign failures. + +Deployment-platform pull authorization is also separate. Configure the Cloud +Run, Kubernetes, Swarm, or other platform identity to read the exact repository +and digest. Do not pass the Package token into Deploy or mount OCI Cosign keys +through `runtimeFiles`. + +## Retry Decision + +Safe to retry after correcting the cause, because Rush Delivery guarantees the +destination registry was not mutated: + +- provider metadata/selection and ownership validation; +- missing provider credential value; +- Cosign key preflight; +- selected filesystem validation/materialization; +- Docker build; +- SPDX generation/validation; and +- Grype execution, report validation, or policy. + +Safe to retry automatically only as a bounded individual operation: + +- pre-mutation trusted-TLS readiness/capability probe; and +- immutable registry read whose response cannot alter release state. + +Require inspection and a human/policy decision before retry: + +- publish request or returned-reference validation; +- provenance/sign/attest/verify or local-evidence finalization; +- any transport failure after mutation may have begun; +- multi-target failure with earlier published siblings; and +- Deploy execution failure. + +Do not treat an infrastructure label as proof of non-mutation. Preserve the +original sanitized failure class when retries are exhausted. + +## Sanitized Diagnostic Bundle + +Collect only what is necessary: + +- Rush Delivery version/tag and exact Dagger CLI/engine version; +- entrypoint, dry/live mode, event type, selected target names, and provider + name; +- full source SHA and non-secret source repository URL; +- registry authority and expected repository path; +- canonical digest references already present in sanitized framework errors; +- failure stage and whether publication may have started; +- the package manifest only after checking that it contains no sentinel or + unexpected field; +- SHA-256 values and paths for local evidence, not key material; +- pinned Syft/Grype/Cosign versions and image digests; +- Grype database build/status metadata and network failure class; +- provider audit/event identifiers and a complete subject/associated-package- + version inventory; and +- reviewed logs with values redacted at collection time. + +Never include: + +- workflow/deploy/release env files or their raw lines; +- registry username/token values or bearer/basic-auth headers; +- private key, public key, signing password, or fingerprints derived by dumping + key files into logs; +- Docker `config.json`, base64 auth, Dagger Secret objects, or secret-bearing + command arguments; +- image filesystem/history dumps until they have passed a credential-sentinel + review; or +- verbose Dagger/provider traces that have not been proven secret-safe. + +When reporting a credential problem, provide only the provider name, credential +role, secret-manager version identifier, and failure stage. When reporting a +partial publication, provide canonical subject references and cleanup status, +never credentials. + +The source repository URL itself must be a public coordinate: do not put tokens +in userinfo, query strings, or fragments. Rush Delivery rejects those channels +without echoing the submitted locator; use the explicit Source authentication +input instead. diff --git a/docs-versions/versioned_docs/version-v0.9.0/oci-application-images.md b/docs-versions/versioned_docs/version-v0.9.0/oci-application-images.md new file mode 100644 index 0000000..b4c4ea8 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/oci-application-images.md @@ -0,0 +1,882 @@ +--- +id: "oci-application-images" +title: "OCI Application Images" +sidebar_label: "OCI Application Images" +description: "Publish verified application images and deploy immutable digests." +--- + +Rush Delivery `v0.9.0` can package a deploy target as a single-platform OCI +image, publish it, sign and attest the immutable digest, and hand that digest to +project-owned Deploy code. This page is the production contract and operator +runbook. Follow the [end-to-end tutorial](../tutorial/oci-application-images) +for a first deployment, use the [registry recipes](../oci-registry-recipes) to +configure a provider, and use the +[troubleshooting guide](../oci-application-image-troubleshooting) during an +incident. + +Application images are opt-in. Projects whose selected artifacts are only +`directory` or `rush_deploy_archive` do not need an application-image provider, +OCI credentials, or a configuration change. For the complete compatibility +boundary, see the [v0.8.1 to v0.9.0 upgrade guide](../upgrade-v0-9-0). + +## Architecture And Trust Boundaries + +```text +project source + | + v +Source acquisition --> initial metadata validation (provider file skipped) + | + v +Detect --> selected package targets --> conditional provider activation + | (metadata and names only) + v +Rush install + Rush Build + | + v +Package barrier + |-- validate/materialize every selected filesystem artifact + |-- one offline Cosign key-pair preflight per selected provider + `-- prepare every selected OCI target in parallel + build --> export exact subject --> SPDX SBOM --> Grype scan + | + | starts only after every preparation succeeds + v +ordered OCI finalization, one selected target at a time + publish --> validate returned digest --> provenance --> sign + --> attest SPDX + provenance --> verify all three + | + v +packaged directory (trusted release-control bundle) + |-- .dagger/runtime/package-manifest.json unsigned + |-- .dagger/runtime/application-image-credential-capability.json + | names only; internal; named providers only + `-- .dagger/runtime/evidence//* locally hashed + | + v +Deploy preflight for every selected target + manifest invariants --> expected source SHA --> credential-name boundary + --> local evidence hashes + | + v +project-owned Deploy script + `-- consumes repository@sha256:...; no rebuild or tag lookup +``` + +There are three separate trust claims: + +1. Package uses the configured public key to cryptographically verify the + digest-bound signature and the SPDX and provenance attestations in the + registry. +2. Deploy validates an already supplied manifest and local evidence bundle. It + does not contact the registry, resolve provider credential values, or run + Cosign again. For an accepted named-provider planned OCI artifact or a + published OCI artifact, it uses the names-only boundary frozen by Package so + provider credential names cannot be projected into project Deploy code. + Older bundles without that internal handoff fall back to provider metadata. +3. The operator protects the complete packaged directory and supplies an + independently trusted full Git SHA and bundle identity. Rush Delivery cannot + detect an attacker who can replace both an unsigned manifest and all evidence + consistently. + +## Metadata Contract + +### OCI package target + +Declare one artifact in `.dagger/package/targets/.yaml`. The target name +must agree with the Rush project, services mesh, package filename, and Deploy +target. The complete constraints are in the immutable +[`v0.9.0` package-target schema](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/package-target.schema.json). + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +name: control-plane-api +artifact: + kind: oci_image + context: apps/control-plane-api + dockerfile: apps/control-plane-api/Dockerfile + image: control-plane-api + platform: linux/amd64 + scan: + fail_on: + - high + - critical + ignore_file: .dagger/application-images/grype.yaml +``` + +| Field | Required | Contract | +| --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Yes | One evidence-safe path segment made from ASCII letters, digits, `@`, `.`, `_`, or `-`, but not `.` or `..`. It must match the metadata filename and a Rush project during metadata-contract validation. OCI names containing `/`, `\\`, whitespace, or another path separator fail during metadata parsing/planning before Rush Build. | +| `artifact.kind` | Yes | Exactly `oci_image` for this artifact contract. | +| `artifact.context` | Yes | Normalized repository-relative directory, or `.` for the repository root. | +| `artifact.dockerfile` | Yes | Normalized repository-relative file contained by `context`; it cannot be the context directory itself. | +| `artifact.image` | Yes | Lowercase relative repository suffix. It contains no registry, tag, or digest. `/`, `.`, `_`, and `-` are allowed only in normalized name segments. | +| `artifact.platform` | Yes | One normalized OCI platform such as `linux/amd64`. `v0.9.0` supports exactly one platform. | +| `artifact.scan.fail_on` | Yes | Non-empty unique list drawn from `critical`, `high`, `medium`, `low`, and `negligible`. This is an exact set, not a threshold. | +| `artifact.scan.ignore_file` | No | Normalized repository-relative path to a Grype YAML configuration. Rush Delivery passes it to Grype with `--config`. | +| `build.pass_env` | No | Host names projected unchanged into Rush Build. Active application-provider credential names are forbidden. | +| `build.map_env` | No | Output name to host-source-name mapping. Both sides are checked against active provider credential names. | +| `build.dry_run_defaults` | No | Dry-run fallback values for Build inputs. Active provider credential names are forbidden. | + +All paths resolve from the repository root. The normal `workflow` and +`buildAndPackageDeployTargets` entrypoints run Rush Build before Package, so the +Dockerfile can consume compiled output. Standalone `packageDeployTargets` +accepts an already-built directory and does not run Build. That directory is a +trusted input: Package snapshots the provider credential-name boundary it sees +at invocation and cannot recover metadata from before an independently run +Build. Prefer `buildAndPackageDeployTargets`, which captures the boundary before +Build and carries it through Package. + +The final repository is: + +```text +// +``` + +Image suffixes selected together must not collide in the same provider +namespace. Rush Delivery uses the deterministic navigation tag +`sha-`; a collision would make two targets contend for one tag. +Deploy never consumes that tag. + +### Application-image provider + +Providers live only at `.dagger/application-images/providers.yaml`. They are +independent of source, toolchain-image, Rush-cache, npm, and deployment-platform +authentication. See the immutable +[`v0.9.0` provider schema](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/application-image-providers.schema.json). + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + release: + kind: oci_registry + registry: ghcr.io + repository_prefix: example/rush-delivery-images + username_env: RD_OCI_USERNAME + token_env: RD_OCI_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +| Field | Required | Contract | +| --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `providers.` | Yes | Normalized lowercase provider name; `off` is reserved. One named provider is selected for all OCI targets in an invocation. | +| `kind` | Yes | Exactly `oci_registry`. | +| `registry` / `registry_env` | XOR | Choose a static lowercase authority or the name of a public Package routing value. Authorities allow an optional port but no scheme/path. | +| `repository_prefix` / `repository_prefix_env` | XOR | Choose a static normalized lowercase repository path or the name of a public Package routing value. Values contain no registry, tag, digest, or interpolation. | +| `username_env` | Yes | Globally unique environment name containing the registry username. The value is a framework-owned non-secret string required by Dagger registry authentication and is never projected to project code. | +| `token_env` | Yes | Environment name containing the registry token/password. The selected live value becomes a Dagger secret immediately. | +| `signing_key_env` | Yes | Environment name containing a password-protected Cosign private key. Literal `\n` sequences are decoded. | +| `signing_password_env` | Yes | Environment name containing the Cosign private-key password. | +| `verification_key_env` | Yes | Environment name containing the matching Cosign public key. Literal `\n` sequences are decoded. | + +All five credential names must be distinct within a provider and globally +unique across every declared provider. Public coordinate names must be distinct +from one another and every application/Rush-cache/toolchain/npm/source +credential capability, `GIT_SHA`, `DRY_RUN`, and `ARTIFACT_*`. Validation +rejects aliases before reading any value; +this prevents a secret role from being reused through Dagger's non-secret +registry-username channel and keeps diagnostics names-only. Rush Delivery also +detects a provider name projected into project Build, npm Release, or Deploy +code, but it cannot detect the same underlying secret value copied under a +different name. + +### Provider activation + +Provider activation follows the selected artifacts, not a global option that +may be unused. + +| Selected package artifacts | Provider option | Run | Result | +| -------------------------- | ----------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| No OCI artifact | Any value, including malformed or unknown | Dry or live | Ignore the unused option. Do not load provider metadata or credentials and do not run OCI tools. | +| OCI artifact | `off` | Dry | Emit relative planned image intent. Do not load provider metadata or credentials. | +| OCI artifact | Named | Dry | Load and validate the provider file, resolve only required public coordinates, validate the planned repository and cross-file boundary. Do not resolve/use provider credentials, create secrets, or run OCI tools. | +| OCI artifact | `off` | Live | Fail before Rush Build, image build, destination-registry access, or Deploy. Source acquisition may already have run. | +| OCI artifact | Named | Live | Resolve/validate public coordinates and cross-file ownership before Build; resolve only the selected provider's five credential values when live Package starts. | + +Invocation-scoped execution initially validates the repository without parsing +the provider file, then Detect and package planning decide whether OCI is +selected. This keeps filesystem-only execution independent of unused OCI +metadata. The explicit `validateMetadataContract` entrypoint is intentionally +stricter: it validates every provider file that is present and checks all +provider credential-name collisions across repository metadata, even without a +particular invocation selection. + +For `workflow`, coordinates come from the equal-only merge of `workflowEnvFile` +and `deployEnvFile`; conflicting duplicates fail. Standalone Package producers +use only `deployEnvFile`. Package normalizes the coordinates once and threads +the canonical repository through publication, evidence, manifest, and cleanup. +Deploy never reloads provider metadata or the current environment to rebuild a +repository. Follow the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles). + +Standalone `deployRelease` applies the same principle to the supplied manifest. +After manifest/source preflight succeeds, if no selected artifact is a published +OCI artifact or a planned OCI artifact with `repository`, it does not read +application-provider metadata. If any selected artifact meets either condition, +Deploy uses the names-only credential capability that Package wrote after Build +and protects the five credential names from **every** provider that was declared +before a composed Build, across all selected Deploy targets. A planned artifact +can reach this boundary only in a dry run. Deploy does not select a provider, +resolve a credential value, authenticate to a registry, or run Cosign. For +compatibility with an older package bundle that has no capability handoff, +Deploy reconstructs the same names-only boundary from +`.dagger/application-images/providers.yaml`. A provider-off planned OCI bundle +has no `repository` and remains provider-independent. Never edit or selectively +copy `.dagger/runtime` files between Package and Deploy; transport the complete +packaged directory under one externally protected identity. + +## Entrypoints + +The public Dagger API uses camelCase names; the CLI renders them in kebab case. +All directory-returning package entrypoints write +`.dagger/runtime/package-manifest.json` into the returned directory. + +| Entrypoint | OCI inputs | Output and production use | +| ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `workflow` | `gitSha`, `applicationImageProvider`, source coordinates, and workflow/deploy env files | Composes Source, Detect, Build, Package, and Deploy. It is the normal all-in-one path and returns Deploy JSON text. OCI publication completes before Deploy starts. | +| `packageDeployTargets` | Already-built `repo`, CI-plan file, full `gitSha`, optional source URL, deploy env file, provider, and dry/live mode | Packages selected artifacts and returns a Dagger directory. It never runs Rush Build. The already-built directory and its provider/deploy metadata are trusted inputs at Package invocation; prefer the combined producer when Build could modify metadata. | +| `buildAndPackageDeployTargets` | Source `repo`, CI-plan file, full `gitSha`, optional source URL, deploy env file, provider, and dry/live mode | Runs Build then Package and returns one packaged directory. This is the preferred split-stage producer. | +| `deployRelease` | Packaged `repo`, full expected `gitSha`, selected targets, package-manifest file, deploy env, and dry/live mode | Validates the supplied bundle and returns Deploy JSON text. It neither rebuilds nor resolves a tag. `applicationImageProvider` is not an input because registry publishing is already complete. For a selected named-provider plan or published OCI artifact, it uses Package's frozen names-only capability (or the provider-metadata fallback for an older bundle) and rejects every selected Deploy projection of a protected credential. | + +See [Entrypoints](../entrypoints) for every general input. A package operation +requires a full 40-character hexadecimal Git SHA for OCI intent, including dry +runs, and normalizes it to lowercase. Live publication also uses the normalized +SHA for the source label, provenance, and navigation tag. + +The optional source repository locator is public provenance and label data, not +an authentication channel. Rush Delivery accepts absolute Git, HTTP(S), or SSH +repository URLs and narrowly validated `git@host:path` locators. It rejects URL +password/userinfo (except the literal SSH user `git`), query strings, fragments, +whitespace, control characters, and arbitrary SCP-like strings without echoing +the rejected value. Supply source authentication only through the explicit +Source capability. + +## Capability And Environment Ownership + +Supplying an env file makes values available to framework coordination; it does +not authorize every stage to project every value. Metadata and explicit adapter +inputs define the capability boundary. + +| Capability | Values it may consume | Application-provider credential rule | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Source acquisition | The explicitly named source auth token and optional username | May read a provider-named variable only if the caller separately names it as Source auth. No automatic reuse occurs. Prefer a dedicated read-only source token. | +| Toolchain-image adapter | Names declared by the selected toolchain provider | Framework-owned explicit use is retained. It does not project application credentials into project code. Prefer separate credentials. | +| Rush-cache adapter | Names declared by the selected cache provider | Framework-owned explicit use is retained. Prefer a separately scoped cache credential. | +| Rush Build | Only `build.pass_env`, `build.map_env`, and dry-run defaults | Every credential name declared by every provider in the active provider file is rejected on both sides of mappings and in pass/default fields. | +| OCI Package tools | Exactly the selected provider's username, token, private key, password, public key, and generated Docker config | The token, key material, password, public key, and Docker config are Dagger secrets. The username remains framework-owned and is not projected or returned, but Dagger's progress graph may display it because registry auth accepts a plain username. Preparation receives no provider secrets. | +| npm Release | The npm `auth.token_env` and npm lifecycle environment | In a composed workflow that activates an application provider, an application-provider credential name is rejected as npm auth. | +| Deploy-tag and release-Git adapters | The explicitly configured Git token | Framework-owned explicit use is retained. It is not automatic application-provider projection. | +| Project Deploy script | Only its runtime metadata plus Rush Delivery's artifact/control variables | Provider credential names are rejected from all runtime projection and host-file channels. Package freezes the pre-Build name-only boundary for composed and split-stage Deploy; an older bundle without that internal handoff falls back to current provider metadata. Registry publishing credentials never become deployment-platform pull credentials. | + +The protected-name check covers `username_env`, `token_env`, +`signing_key_env`, `signing_password_env`, and `verification_key_env` from every +declared provider once a provider is active, or once standalone Deploy selects +a named-provider/published OCI artifact. Errors identify the provider, target, +metadata field, and environment name, but never read or print the value. + +Public entrypoints parse a supplied aggregate env file for every configured +capability. Consequently, a dry/no-OCI invocation that receives such a file may +read its bytes while parsing it; the application-provider subsystem does not +index, resolve, use, log, or convert provider-named entries into Dagger secrets. +For the narrowest boundary, omit live OCI values—and preferably the entire env +file—from dry/no-OCI calls. Registry usernames must be non-secret because +Dagger's client progress/call graph can show the plain registry-auth username. + +### Framework-owned Deploy variables + +Rush Delivery reserves all names beginning with `ARTIFACT_`, including names it +may add later, plus `GIT_SHA` and `DRY_RUN`. Deploy metadata cannot declare a +reserved name in `runtime.env`, `runtime.pass_env`, a `runtime.map_env` output, +`runtime.dry_run_defaults`, `runtime.required_host_env`, or a host-path +`runtime.file_mounts[].source_var`. Equal values are still an ownership +collision. A `map_env` source name is a host lookup rather than a project output, +but it remains subject to provider credential protection when OCI is active. + +| Variable | Filesystem artifact | Planned OCI in a Deploy dry run | Published OCI | +| ------------------------------- | -------------------------- | -------------------------------------- | ---------------------------------------------- | +| `ARTIFACT_PATH` | `/workspace/` | Absent | Absent | +| `ARTIFACT_KIND` | Absent | `oci_image` | `oci_image` | +| `ARTIFACT_IMAGE_NAME` | Absent | Relative `artifact.image` | Relative `artifact.image` | +| `ARTIFACT_IMAGE_REFERENCE` | Absent | Absent | Exact `repository@sha256:` | +| `ARTIFACT_IMAGE_REPOSITORY` | Absent | Present only for a named-provider plan | Present | +| `ARTIFACT_IMAGE_DIGEST` | Absent | Absent | Lowercase `sha256:<64 hex>` | +| `ARTIFACT_IMAGE_PLATFORMS_JSON` | Absent | JSON array containing one platform | JSON array containing one platform | +| `ARTIFACT_SOURCE_REVISION` | Absent | Full source SHA | Full source SHA | +| `ARTIFACT_EVIDENCE_DIR` | Absent | Absent | `/workspace/.dagger/runtime/evidence/` | +| `GIT_SHA` | Current invocation SHA | Current invocation SHA | Independently supplied expected SHA | +| `DRY_RUN` | `1` or `0` | `1` | `0` | + +Rush Delivery constructs project and framework environments separately, rejects +collisions, and applies framework values last. Each result and dry-run summary +comes from that invocation's final environment. Dry and live summaries are not +byte-identical because `DRY_RUN`, defaults, published identity, and evidence +differ. + +### Workspace and evidence isolation + +For `runtime.workspace.mode: full`, Deploy receives the full packaged workspace +except `.dagger/runtime/evidence`. For a partial workspace, every requested +file/directory is selected from the same evidence-filtered view. Explicitly +requesting the evidence directory or one of its descendants is rejected; asking +for a parent such as `.dagger` is allowed, but the framework evidence subtree is +still removed. + +After generic workspace assembly, Rush Delivery mounts only the current +published OCI target's already-verified evidence at +`ARTIFACT_EVIDENCE_DIR`. Other OCI targets' evidence is unavailable, and +filesystem or planned OCI targets receive no evidence mount. Deploy-platform +credentials can be supplied through `runtimeFiles` and `runtime.file_mounts`, +but OCI registry tokens and Cosign keys must never be placed there. +Repository-backed host-path sources are normalized before use and cannot point +at `.dagger/runtime/evidence` or any descendant. File resolution also uses an +evidence-stripped repository view, so a safe-looking symlink cannot resolve +back into another target's evidence. Ordinary symlinks whose targets remain +outside that subtree continue to work. Package materializes the post-Build +non-runtime `.dagger` tree as a concrete directory, preserving project-owned +outputs there, and creates a fresh `.dagger/runtime` before writing the +manifest, frozen credential-name capability, or evidence. Deploy fails closed +if a supplied bundle aliases +`.dagger`, `.dagger/runtime`, or `.dagger/runtime/evidence` through a symlink. +The destination of either file +mount form is normalized independently and cannot equal, descend from, or be a +parent that could mask `/workspace/.dagger/runtime/evidence`. These checks run +in the schema/parser where representable and again at execution for direct or +legacy internal callers. + +## Package Security Pipeline + +### Key normalization and offline preflight + +Flat env files contain one `NAME=value` per line. Public examples therefore +store PEM line breaks as the two characters `\n`; Rush Delivery decodes them. +Raw multiline PEM is accepted by internal normalization but is not a valid +multiline flat-env record. +If a physical record is malformed, its diagnostic contains only the line number +and a redaction marker; it never repeats the raw line, invalid name, or value. + +Rush Delivery first materializes the pinned Cosign image and a digest-pinned +static BusyBox shell helper without attaching provider secrets. It copies only +the BusyBox binary into the Cosign container. Pull, DNS, or TLS failure for +either image at that point is a sanitized preflight-tool availability error, +not a credential-role diagnosis. After normal Rush Build and selected +filesystem package materialization, but before any selected application image +is built, scanned, authenticated to its destination, or published, it runs one +Cosign preflight for the selected live provider. The preflight: + +1. decrypts the password-protected private key; +2. derives its public key; +3. signs a fixed local challenge; +4. verifies the challenge with the derived key; and +5. verifies it with the configured public key. + +This proves that the private key is usable with the supplied password and that +the public key matches. Failures expose only the provider and credential role. +The four Cosign commands run in one shell exec so the challenge, derived public +key, signature bundle, and captured tool diagnostics exist only on one temporary +mount. The operation is cryptographically offline, but Dagger may need ordinary +network access to pull both pinned preflight tool images before it runs. + +Keep the private key and password in a protected secret manager. Keep old public +keys and the release bundles that they verified for at least as long as the +associated image can be deployed or audited. The v2 manifest does not record a +key fingerprint, so key inventory, activation time, rotation, revocation, and +release-to-key mapping are operator records. Losing the private key prevents new +signatures; losing the matching public-key history weakens later auditability. +Use Sigstore's current +[self-managed-key guidance](https://docs.sigstore.dev/cosign/key_management/signing_with_self-managed_keys/) +for key generation and custody, while retaining Rush Delivery's stricter +password-protected PEM and flat-env encoding requirements. + +### Preparation barrier and ordered finalization + +Live packaging reaches the OCI phases only after normal Rush Build and all +selected filesystem package validation/commands have materialized successfully: + +- Preparation builds each selected image, exports that exact container subject, + validates the SPDX document, and scans the subject. OCI preparations run in + parallel and all started work is awaited. Selected directory/archive + validations and materialization also complete before publication. +- Finalization runs one target at a time in stable selected-target order. It + publishes, validates the returned reference, creates provenance, signs, + attaches SPDX and provenance attestations, verifies them, and constructs local + evidence. + +If any preparation fails, no selected application image is published. If a +finalization fails, later targets are not started. The error reports earlier +known published siblings, a canonical reference for the failed target when one +is known, later skipped targets, and a sanitized cleanup warning. All operations +already started are awaited. + +Publication is not transactional. Rush Delivery does not attempt +provider-specific deletion and does not write a successful package manifest or +start Deploy after finalization failure. A registry can still contain an image, +the `sha-` navigation tag, a signature, or one of the attestations. +Inspect before retrying. + +### Vulnerability policy + +`scan.fail_on` is the exact set of rejected normalized severities. It is not a +threshold: + +- `[high]` rejects High and does not reject Critical; +- `[critical]` rejects Critical and does not reject High; and +- production policy commonly uses `[high, critical]` to reject both. + +Rush Delivery fails closed if Grype output is not an object with a `matches` +array, or if an evaluated match lacks a non-empty vulnerability ID or a +supported severity. An explicit empty `matches` array is valid. `unknown` is not +a selectable severity. + +`scan.ignore_file` is an ordinary repository-owned Grype configuration passed +through `--config`; it is not a Rush Delivery suppression format. A minimal +configuration is: + +```yaml +# Owner: platform-security +# Reason: no active exception; add only a narrow supported Grype rule. +# Review/expiry: not applicable +# Removal follow-up: delete each rule when its remediation ships. +ignore: [] +``` + +For a real exception, use only fields supported by the pinned Grype +configuration, and record reason, owner, review/expiry date, and removal action +in adjacent comments or a governed external record. The canonical example is +[`grype.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/.dagger/application-images/grype.yaml). + +The Grype executable is immutable, but its vulnerability database is not. The +container uses a cache keyed by the Grype version and may check/download +network-supplied database data. Findings can change between otherwise identical +runs as feeds and mappings change. Production runners need outbound access to +the database service or a deliberately governed cache/mirror, sufficient +download time, and monitoring for freshness. Database unavailability, stale-age +enforcement, malformed output, or unsupported severity fails the Package gate; +do not reinterpret it as a clean scan. + +### Tool and Cosign mode + +| Tool | Version | Digest-pinned image | +| ------------------------ | ------------- | -------------------------------------------------------------------------------------------------------- | +| Syft | `1.50.0` | `anchore/syft@sha256:1288ea4c8b38767b4e620c1e312c8cb26b6e887a99b4f07ab6cd19fc6f225026` | +| Grype | `0.116.1` | `anchore/grype@sha256:1e71065c0a4cff3e6bd3b8add525ffac4343eb4971694eb90a31cf6d4d3e85db` | +| Cosign | `3.1.2` | `ghcr.io/sigstore/cosign/cosign@sha256:d91bc4e7e95e8d2f549c747a72dc174f90579e410a1695f57f686674f84ce849` | +| BusyBox preflight helper | `1.37.0-musl` | `busybox:1.37.0-musl@sha256:fc6dddc4c44b1bfe37f41cae8e67d1693828e8f42a91862816d7953e2c9d3f23` | + +Signing, challenge signing, and both attestations use +`--use-signing-config=false` to pin the explicit offline key flow and +`--tlog-upload=false` to disable transparency-log upload. Signature and +attestation verification use the configured key with +`--insecure-ignore-tlog`. + +All six registry commands (sign, two attestations, signature verification, and +two attestation verifications) also pin `--new-bundle-format=false`. With the +pinned Cosign `3.1.2`, this deliberately selects digest-derived legacy `.sig` +and `.att` tag attachments rather than the OCI 1.1 Referrers API. The two +attestations share the current `.att` image: the second operation reads the +existing attachment, appends provenance, and writes the combined attachment. +A registry may retain the superseded first-attestation manifest as an untagged +historical version, so object counts are inventory—not proof of completeness. +Package verifies the signature and each attestation type independently. This +flag does not apply to the local preflight challenge bundle, enable legacy +Docker media types, or permit insecure transport. + +The compatibility mode is pinned to Cosign `3.1.2`; that CLI marks the flag as +deprecated. A future Cosign upgrade must re-prove the flag contract, registry +storage behavior, cleanup, and live acceptance before changing the pin. Do not +run concurrent Package finalization or key rotation for the same digest: the +shared `.att` attachment is a read/append/write object. Serialize publishers for +one subject to avoid lost updates or mixed-key verification failures. + +For the six registry Cosign commands, Rush Delivery redirects stdout to distinct +regular files under `/tmp/rush-delivery-cosign-*.stdout` inside the ephemeral +Cosign container. Dagger `v0.20.7` cannot use `/dev/null` for its +`redirectStdout` option: it rejects that special path before starting Cosign. +The temporary stdout files are not exported, retained, or treated as evidence; +the validated local evidence documents and successful independent verification +remain the Package contract. + +This private-registry-friendly mode proves that the configured key verified the +digest-bound subject signature and the required attestations during Package. It +does not prove Rekor inclusion, keyless workload identity, public transparency, +trusted timestamping, public auditability, or a new cryptographic verification +during Deploy. + +### Dagger execution and caching + +Rush Delivery gives every public Dagger function an explicit cache scope. +Session-stable inspection calls may reuse results within one session; +state-sensitive calls that execute project code, observe mutable external state, +or can create side effects opt out of Dagger function-result caching. + +Function caching and container layer caching are separate. To make repeated, +otherwise identical invocations actually rerun mutable or security-sensitive +operations, Rush Delivery injects a fresh random **non-secret** input before +Cosign preflight/publication, each Grype scan, project Deploy execution, and npm +release. Normal deterministic image-build and safe container-layer caching still +apply; this is not a claim that the whole graph is uncached. See Dagger's +official [function-caching](https://docs.dagger.io/extending/function-caching/) +and [secret-handling](https://docs.dagger.io/extending/secrets/) guidance. + +Secrets and derived authorization values must never be written into a cached +filesystem layer. The npm release path therefore uses a static Git askpass +helper whose file contains environment-variable names only; the token is read +from a Dagger secret environment by the Git process and is not stored as a +Basic header in `.git/config`. Cosign preflight's challenge, derived public key, +signature bundle/output, and suppressed tool diagnostics live within one exec on +a dedicated temporary mount rather than a persisted execution layer. + +## Evidence And Deploy Verification + +Each published artifact points at three local files: + +| Evidence | Local file | Registry object | Meaning | +| ------------------ | ----------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| SPDX SBOM | `sbom.spdx.json` | Signed `spdxjson` attestation | Syft-produced SPDX 2.3 JSON for the exact prepared subject. Package validates its minimum document shape. | +| Vulnerability scan | `scan.json` | None | Grype JSON for the prepared subject after the exact-set policy passed. This report is local evidence, not an attestation. | +| Provenance | `provenance.json` | Signed `slsaprovenance1` attestation | Source revision, source URI, target/build parameters, builder identity, and published subject digest. | + +`evidence.signature.verified: true` records successful Package-time verification +of the subject signature and required attestations with the configured public +key. `signature.reference` is the immutable image subject used for Cosign +lookup, not a portable address for a standalone signature object. + +| Check | Package | Deploy | Operator/platform | +| ---------------------------------------------------------------------------- | ---------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| Registry returned a lowercase digest for the expected repository | Yes | Rechecks manifest agreement only | Retain that digest. | +| Subject signature is cryptographically valid under the configured public key | Yes, with Cosign | No; requires the strict `verified: true` assertion | Retain key history and the registry signature attachment. | +| SPDX and provenance attestations are cryptographically valid | Yes, with Cosign | No; requires manifest assertions and local files | Retain the combined attestation attachment and evidence. | +| Local evidence bytes match manifest SHA-256 values | When constructing manifest | Yes, before any live deploy wave | Protect the complete bundle from coordinated replacement. | +| Artifact source revision matches release revision | Builds provenance/manifest from full SHA | Compares every selected OCI artifact to supplied `gitSha` | Supply the expected SHA from protected metadata outside the bundle. | +| Deployment platform pulls the same image | Provides digest reference | Passes reference to project script unchanged | Configure target-platform registry read identity and enforce digest pulls. | +| Portable bundle is authentic | No signed bundle contract | No | Store immutably/access-controlled and record its checksum or artifact identity externally. | + +Deploy parses strict v2 manifests, requires lowercase digest-only references, +checks exact repository/reference and source-revision agreement, validates +target-contained evidence paths, and hashes local evidence before the first live +deploy wave. A live Deploy rejects a planned OCI artifact. It performs no +registry query and no Cosign operation. + +## Manifest Examples + +The exact schema is +[`package-manifest.schema.json`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/package-manifest.schema.json). +All hashes below are synthetic but full length. + +### Legacy filesystem-only manifest + +Directory/archive-only output keeps the unversioned shape: + +```json +{ + "artifacts": { + "webapp": { + "deploy_path": "apps/webapp/dist", + "kind": "directory", + "path": "apps/webapp/dist" + } + } +} +``` + +### Planned OCI manifest + +A named-provider dry run includes `repository`; provider `off` omits it. +Neither form contains a digest, reference, evidence, or success assertion. + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "repository": "ghcr.io/example/rush-delivery-images/control-plane-api", + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "planned" + } + } +} +``` + +### Published OCI manifest + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "evidence": { + "provenance": { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "format": "slsa-provenance-v1", + "path": ".dagger/runtime/evidence/control-plane-api/provenance.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "sbom": { + "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "format": "spdx-json", + "path": ".dagger/runtime/evidence/control-plane-api/sbom.spdx.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "scan": { + "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "path": ".dagger/runtime/evidence/control-plane-api/scan.json", + "policy": ["high", "critical"], + "result": "passed", + "scanner": "grype-0.116.1" + }, + "signature": { + "kind": "sigstore", + "reference": "ghcr.io/example/rush-delivery-images/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "verified": true + } + }, + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "reference": "ghcr.io/example/rush-delivery-images/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "repository": "ghcr.io/example/rush-delivery-images/control-plane-api", + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "published" + } + } +} +``` + +### Mixed v2 manifest + +Filesystem fields stay unchanged inside the strict v2 envelope: + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "webapp": { + "deploy_path": "apps/webapp/dist", + "kind": "directory", + "path": "apps/webapp/dist" + }, + "control-plane-api": { + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "planned" + } + } +} +``` + +### Deploy results + +A filesystem result retains `artifactPath`: + +```json +{ + "artifactPath": "/workspace/apps/webapp/dist", + "output": "webapp deployed\n", + "status": "success", + "target": "webapp", + "wave": 1 +} +``` + +A published OCI result has image identity and never fabricates +`artifactPath`: + +```json +{ + "artifactImage": "control-plane-api", + "artifactKind": "oci_image", + "artifactReference": "ghcr.io/example/rush-delivery-images/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "output": "control-plane-api deployed\n", + "status": "success", + "target": "control-plane-api", + "wave": 1 +} +``` + +A planned OCI dry-run result has `artifactImage` and `artifactKind` but omits +`artifactReference`. + +## Production Readiness Checklist + +Before the first live publication: + +- Pin the Action/module and editor schemas to `v0.9.0`. +- Validate metadata with `validate-metadata-contract`, then run provider-off and + named-provider dry runs. +- Use a trusted-TLS registry whose image, signature, and attestation behavior + has passed a pre-production live test. See + [Registry recipes](../oci-registry-recipes). +- Create every required destination repository and give the publisher only + image plus digest-derived signature/attestation tag push and verification-read + access. Give cleanup to a separate operator when practical. +- Configure the target platform with a distinct pull-only identity. +- Generate a password-protected Cosign key, protect the private key/password, + record the public-key inventory, and test rotation and recovery. +- Store PEM values with literal `\n` in flat env inputs and verify a local + encode/decode round trip without printing them. +- Set a deliberate exact scan policy and govern every Grype ignore rule. +- Allow the pinned tool-image pulls and Grype database traffic, and monitor + database freshness. +- Configure registry retention for digest subjects, navigation tags, signatures, + and attestations for at least the release and rollback window. +- Store the complete packaged directory as a mode/symlink-preserving archive in + immutable or access-controlled storage. Record its checksum/identity and full + Git SHA in protected release metadata outside that archive. +- Make Deploy consume `ARTIFACT_IMAGE_REFERENCE` unchanged and use + `ARTIFACT_EVIDENCE_DIR`; do not resolve the navigation tag. +- Set the GitHub Action `docker-socket: ""` for OCI-only jobs. A mounted host + Docker socket grants project Deploy code effective control over the runner's + Docker daemon and can bypass Dagger workspace and secret-file isolation by + asking that daemon to mount host paths. The non-empty Action default exists + only for compatibility with trusted legacy project deploy scripts that invoke + Docker; never expose it to untrusted checkout code. +- Test partial-publication discovery, provider-specific cleanup, retained-digest + rollback, and deployment-platform pull authorization. + +## Failure And Side-Effect Matrix + +“Manifest” below means a successful manifest for this Package invocation. +Errors and logs may include target/provider names, stages, and canonical digest +references, but never credentials or Docker auth payloads. + +| Failure point | Registry mutation possible? | Successful manifest from this attempt / Deploy? | Safe diagnostic | Retry and cleanup | +| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Package provider file/selection or protected/reserved-name validation | No application-image mutation | No / No | Metadata path, provider/target/field names, and validation text | Correct metadata and retry. | +| Standalone Deploy frozen capability, legacy provider fallback, or credential-name boundary | No new mutation; a prior Package can already have published | Existing supplied bundle only / No target starts | Provider/target/field/environment names only | Restore the complete valid bundle or remove the project projection, rebuild the trusted bundle, and retry Deploy. Never remove runtime handoff or manifest identity files to bypass the check. | +| Selected credential name/value lookup | No | No / No | Provider name, credential role, and secret-manager version/presence status | Correct the missing named value and retry. Never print the env file. | +| Preflight tool availability before key checks | No destination mutation; pinned Cosign and BusyBox image pulls may be attempted | No / No | Pinned image/version, provider name, sanitized network/cache stage | Restore trusted registry/DNS/TLS/cache availability and retry. Do not rotate keys based on this error. | +| Cosign key preflight | No destination mutation; both pinned preflight tools are already available | No / No | Provider, key role, controlled key-version/fingerprint inventory, and sanitized stage | Correct private key, password, or public key and retry. Do not print either key. | +| Filesystem artifact validation/materialization | No OCI publication | No / No | Target, expected relative path, validation, and sanitized filesystem error | Correct the artifact and retry the batch. | +| Docker build | No destination mutation | No / No | Target, platform, context/Dockerfile paths, and sanitized build stage | Correct context/Dockerfile/build output and retry. | +| SPDX generation or structure validation | No | No / No | Target, pinned Syft identity, and sanitized stage error | Fix subject/tool availability and retry. | +| Grype execution, database, report validation, or policy | No | No / No | Target, pinned Grype identity, database status/time, rejected IDs/severities, and sanitized error | Restore database availability/freshness or remediate/govern findings, then retry. Do not weaken fail-closed parsing. | +| Registry publish request | Yes; outcome may be unknown after interruption | No / No | Expected repository, SHA tag, target, audit/event ID, and sanitized transport class | Inspect the subject and every associated tagged or untagged package version first. Clean or retain deliberately; never automatically replay the whole Package flow. | +| Returned-reference validation | Yes | No / No | Expected repository/tag and sanitized returned reference shape | Treat the namespace as mutated, inspect it, and investigate registry/Dagger compatibility before retry. | +| Provenance construction | Yes, with canonical subject known | No / No | Target, canonical subject reference, and sanitized local stage | Inspect and clean the subject/tag as policy requires; fix locally, then retry manually. | +| Cosign sign | Yes; subject and possibly signature exist | No / No | Target, canonical subject, Cosign stage, and key-version inventory; no key bytes | Inspect the subject, attachment tags, and all package versions, then clean or quarantine incomplete objects before a controlled new Package attempt. | +| SPDX or provenance attestation | Yes; earlier signature/attestation may exist | No / No | Target, canonical subject, failed attestation kind, and complete package-version inventory | Inventory the `.sig`/`.att` attachments plus untagged history, then apply provider cleanup/retention policy before retry. | +| Signature or attestation verification | Yes; all objects may exist but are not accepted | No / No | Target, canonical subject, verification kind, key-version inventory, and sanitized failure | Preserve failure evidence, inspect keys and all associated package versions, and clean or quarantine before retry. | +| Local evidence hashing/finalization | Yes; the subject and all verified Cosign objects may exist | No / No | Target, canonical subject, evidence kind/path, and sanitized local stage; no evidence contents unless separately reviewed | Inspect the subject and associated package versions, diagnose local Dagger/evidence handling, and clean or quarantine before a controlled retry. | +| Later target finalization | Earlier siblings may be fully published; failed target may be partial; later targets are not started | No / No | Stable earlier/failed/later target sets and canonical references supplied by the sanitized report | Inspect every earlier/failed target. Never assume batch rollback occurred. | +| Manifest parsing, source SHA, planned-live, repository/reference, path, or evidence-integrity preflight | No new mutation; prior Package objects remain | Existing supplied bundle only / No target starts | External bundle identity/checksum, expected SHA, target, non-secret manifest field/path, and evidence digest | Restore the correct trusted bundle and expected SHA. Do not edit the manifest to force acceptance. | +| Deploy execution | Registry objects and manifest already exist; deployment-platform side effects may occur | Yes / Current and earlier wave work may have started | Target/wave, digest reference, deployment event ID, and sanitized script/platform status | Inspect the target platform before retry. Reuse the same digest; do not rebuild or retag as “rollback.” | + +Only bounded, side-effect-free readiness/capability probes and immutable reads +are candidates for automatic retry. A transport failure after a publish request +may have crossed the mutation boundary. Classify it as unknown/partial, inspect +the unique repository namespace and all associated package versions, then decide cleanup or manual +retry. + +## Trusted Split-Stage Handoff And Rollback + +Treat the packaged directory, manifest, and evidence as one release-control +bundle. Persisting only the manifest is insufficient. + +1. Export the complete result of `build-and-package-deploy-targets`. +2. Create a deterministic `tar.gz` (or an equivalently reviewed format) that + preserves file modes and symlinks. Reject absolute paths, `..` members, and + links that escape the restoration root. +3. Compute SHA-256 over the archive. Store the checksum or immutable CI artifact + identity and the original full Git SHA in protected metadata outside the + unsigned archive. +4. Upload the archive atomically to access-controlled immutable storage. Retain + it for the same window as the image digest and Cosign attachment artifacts. +5. In the protected consumer job, download to a staging location, verify the + externally recorded checksum before extraction, inspect member/link safety, + extract into a new directory, and atomically promote the restored tree. +6. Pass the independently recorded Git SHA as `deploy-release --git-sha` and the + restored manifest as `--package-manifest-file`. Deploy verifies the manifest + revision, frozen credential-name capability, and evidence hashes before any + live wave starts. + +The three framework paths `.dagger`, `.dagger/runtime`, and +`.dagger/runtime/evidence` must be real directories when present in a packaged +Deploy bundle, not symbolic links. Package preserves post-Build project output +elsewhere under `.dagger`, replaces the entire old runtime path, and writes the +new manifest, names-only capability, and evidence below concrete directories. +Standalone Deploy does not repair a supplied bundle: its common preflight +rejects an alias before either a dry or live target runs. + +For rollback, restore an earlier retained archive and verify it against that +release's external checksum/identity and full SHA. Call Deploy with the earlier +SHA and let the script consume the earlier `repository@digest` unchanged. Do not +edit the manifest, rebuild the source, or look up `sha-` to discover the +digest. Confirm first that the registry still retains the subject and all pull +permissions required by the target platform. + +The all-in-one GitHub Action output does not automatically preserve a reusable +packaged directory. Use a raw Dagger package/export step and an explicit CI +artifact handoff for rollback-capable split workflows; the +[split-stage tutorial](../tutorial/oci-application-images/split-stages-and-rollback) +contains the complete command sequence. + +## Key Rotation And Retention + +Use a staged rotation: + +1. Generate a new password-protected key pair in an isolated operator context. +2. Record the new public-key fingerprint, custodian, activation time, and + affected provider outside Rush Delivery metadata. +3. Update the secret manager and public-key variable together, then run a named + dry run. It checks metadata, not values. +4. Run a controlled live canary. The Package preflight proves the new pair + matches before destination mutation. +5. Keep the old public key, bundle checksum/SHA records, manifest/evidence, image + digest, signature, and attestations through every audit/rollback window for + releases signed by the old key. +6. Revoke or destroy the old private key according to organizational policy. + Rotation does not re-sign old releases automatically. + +Registry retention must account for both the subject and its Cosign objects. +Before deleting a digest or navigation tag, discover and inventory associated +signatures and attestations with provider-supported tools. Test cleanup rules in +dry-run/preview mode where the provider supports it. Rush Delivery performs no +automatic deletion. + +## Current Limitations + +- One explicit platform per target; no multi-platform index. +- Dockerfile builds have no Rush Delivery metadata for build arguments, build + secrets, SSH mounts, or Dockerfile `target` selection. +- Provider coordinates support strict static values or environment-selected + public values. There is no interpolation, credential-bearing coordinate, + arbitrary resolver, or Deploy-time repository reconstruction. +- Key-backed Cosign only. No keyless/OIDC identity, Rekor upload/inclusion, + trusted timestamp, or public transparency mode. +- No public custom-CA or insecure-registry option. The destination must be + reachable with trusted TLS from Dagger and Cosign. +- Registry support must include image push, a returned digest, and storage and + retrieval of Cosign's digest-derived `.sig` and `.att` tag attachments. The + OCI 1.1 Referrers API is neither required nor exercised in `v0.9.0`. OCI + conformance alone does not prove the complete Rush Delivery path; test the + exact service. +- No framework-owned Cloud Run, Kubernetes, Swarm, or other vendor deployment. + Project Deploy code owns platform rollout and pull authentication. +- No automatic deletion or transactional registry rollback. +- No signed portable package manifest and no Deploy-time registry/Cosign + verification. +- No key fingerprint in the manifest. +- The local Grype report is not a registry attestation, and its database is a + mutable network/cache input. +- The Action's Docker-socket default is retained for legacy project deploy + scripts. First-class OCI Package operations do not require that socket. + +## Upgrade To v0.9.0 + +Static providers, provider-off/filesystem projects, package-manifest v2, and +digest-only Deploy remain compatible. Environment-selected coordinates are +additive and opt-in. The one migration-sensitive area is Action local-copy, +whose new caller-side policy defaults to bounded exclusions. + +Use the complete [v0.8.1 to v0.9.0 upgrade guide](../upgrade-v0-9-0) for the +compatibility matrix, local-copy inclusion/`legacy` recovery, canary sequence, +new metadata fields, and checksummed launcher installation. Use the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles) +when adopting dynamic public coordinates. diff --git a/docs-versions/versioned_docs/version-v0.9.0/oci-registry-recipes.md b/docs-versions/versioned_docs/version-v0.9.0/oci-registry-recipes.md new file mode 100644 index 0000000..7abcd93 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/oci-registry-recipes.md @@ -0,0 +1,729 @@ +--- +id: "oci-registry-recipes" +title: "OCI Registry Recipes" +sidebar_label: "OCI Registry Recipes" +description: "Configure registry permissions, retention, and cleanup." +--- + +This guide maps the Rush Delivery `v0.9.0` application-image provider contract +to common registries. Start with the production contract in +[OCI application images](../oci-application-images), then complete the +[tutorial](../tutorial/oci-application-images) with a disposable +namespace before using a production repository. + +The recipes are deliberately explicit about test status: + +| Recipe | Repository status | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Provider-neutral contract | Parser/schema/unit tested. A project-controlled trusted-TLS endpoint is the required live release gate; its result must be recorded for each release candidate and is not claimed as continuous per-commit coverage here. | +| GitHub Container Registry (GHCR) | Syntax-reviewed against current GitHub and Dagger documentation. It is the preferred project-controlled release-gate service, but a successful live gate must be recorded for each release candidate. | +| Google Artifact Registry (GAR) | Syntax-reviewed against current Google Cloud documentation; no continuous live Rush Delivery test is claimed. | +| Amazon Elastic Container Registry (ECR) | Syntax-reviewed against current AWS documentation; no continuous live Rush Delivery test is claimed. | +| Docker Hub | Syntax-reviewed against current Docker and Dagger documentation; no continuous live Rush Delivery test is claimed. | + +Production workflow dependencies must be immutable. Third-party actions in this +guide use full 40-character commit SHAs with a release-version comment; update +the SHA and comment together through reviewed dependency automation. Rush +Delivery examples use `@v0.9.0` to identify this guide's release contract. In a +strict consumer workflow, verify that release tag and replace it with the full +release commit SHA before merge. GitHub documents that only the full commit SHA +is immutable and can enforce full-SHA action references in repository or +organization policy; see its +[action security guidance](https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions). + +Before adopting a service, run a live pre-production Package against the exact +registry tier, region, repository policy, and identity configuration you will +use. A vendor's general OCI support is not proof that its current settings +accept both Cosign attestations and the complete verification sequence. + +## Registry Capability Contract + +A compatible endpoint must provide: + +- trusted TLS from the Dagger engine and pinned Cosign container; +- OCI image blob and manifest push; +- a canonical returned image digest; +- authenticated reads during Cosign verification; +- storage and discovery for the subject signature plus SPDX and provenance + attestations; +- retention of the digest and associated Cosign objects for the audit and + rollback window; +- operator-controlled inspection and cleanup; and +- a distinct pull identity for the deployment platform. + +The [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md) +defines image push, digest, tag, and deletion APIs. Cosign also documents a +broad but qualified +[registry support matrix](https://github.com/sigstore/cosign#registry-support). +Rush Delivery `v0.9.0` pins Cosign `3.1.2` and passes +`--new-bundle-format=false` on every registry sign, attest, and verify command. +That mode stores one digest-derived `.sig` attachment and a shared `.att` +attachment containing both attestations. It does not use or require the OCI 1.1 +Referrers API. “Associated Cosign artifact” in this guide means either of those +tag-addressed objects or an untagged historical version—not a claim that the +Referrers API is in use. This is distinct from Cosign's legacy Docker media-type +fallback, which Rush Delivery does not enable. Custom CAs and insecure/HTTP +registries also remain unsupported. Test the exact endpoint rather than relying +on the product name alone. + +Rush Delivery supplies the selected username/token directly to Dagger registry +authentication and mounts a generated Docker config as a Dagger secret for +Cosign. `docker login`, a host Docker CLI, and a Docker socket are not Package +prerequisites. + +## Provider-Neutral Recipe + +Use this shape for a standards-compatible private registry: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + release: + kind: oci_registry + registry: registry.example.com + repository_prefix: platform/application-images + username_env: RD_OCI_REGISTRY_USERNAME + token_env: RD_OCI_REGISTRY_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +For `artifact.image: control-plane-api`, the destination is +`registry.example.com/platform/application-images/control-plane-api`. +`registry` is an authority, not `https://registry.example.com`; metadata does +not interpolate environment variables. + +### Provisioning and permissions + +Use the registry's control plane to create the namespace/repository before the +release. The publishing identity needs the narrow equivalent of: + +- pull/read manifests and blobs, because Package verifies what it wrote; +- initiate, upload, and complete blobs; +- create/update the deterministic `sha-` tag and subject manifest; +- create/read the digest-derived `.sig` and `.att` attachment tags and their + manifests; and +- read those objects for verification. + +Give delete/retention administration to a separate cleanup identity when the +provider permits it. Give the deployment platform only subject pull/read access. + +Map credentials without placing values in metadata: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + application-image-provider: release + docker-socket: "" + dry-run: "false" + deploy-env: | + RD_OCI_REGISTRY_USERNAME=${{ vars.RD_OCI_REGISTRY_USERNAME }} + RD_OCI_REGISTRY_TOKEN=${{ secrets.RD_OCI_REGISTRY_TOKEN }} + RD_OCI_COSIGN_PRIVATE_KEY=${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD=${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY=${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} +``` + +The PEM secrets must contain literal `\n` separators, not actual +line breaks. Restrict live credentials to protected release jobs and trusted +events. The registry username is intentionally a non-secret variable because +Dagger may display it in the registry-auth call graph; never put sensitive data +in that value. Do not send live secrets to fork or untrusted pull-request +execution. + +### Retention and cleanup + +Retain the immutable subject, navigation tag, signature, combined attestation +attachment, +manifest/evidence bundle, public-key history, and external bundle checksum/SHA +record for the same rollback period. Preview lifecycle rules where supported. +Before deleting a partial release, inventory every tagged and untagged package +version. Tag deletion alone may not remove the subject, current attachments, or +superseded attachment history. + +### Repository acceptance topology + +Rush Delivery's repository-maintainer harness uses the canonical public example +with a trusted-TLS endpoint and a cryptographically unique repository namespace. +The official GitHub workflow is locked to the project's own GHCR namespace; +outside that exact repository context the harness requires an explicit endpoint, +repository prefix, retention policy, and harness-owned credentials. These +test-harness settings are not a consumer-facing dynamic provider feature. The +public provider metadata remains static. + +For repository maintainers, the test-only invocation is shaped as follows: + +```sh +acceptance_run="$(node -e \ + 'process.stdout.write(require("node:crypto").randomBytes(16).toString("hex"))')" + +OCI_ACCEPTANCE_REGISTRY=ghcr.io \ +OCI_ACCEPTANCE_REPOSITORY_PREFIX="bootstraplaboratory/rush-delivery-acceptance-${acceptance_run}" \ +OCI_ACCEPTANCE_RETENTION_POLICY=delete-complete-package-on-exit \ +OCI_ACCEPTANCE_CLEANUP_HOOK="$PWD/test/scripts/cleanup-ghcr-acceptance.sh" \ +OCI_ACCEPTANCE_USERNAME="$GITHUB_ACTOR" \ +OCI_ACCEPTANCE_TOKEN="$REGISTRY_TEST_TOKEN" \ +GITHUB_TOKEN="$REGISTRY_TEST_TOKEN" \ +test/scripts/run-oci-acceptance.sh +``` + +Run this from the repository root so the cleanup hook is an absolute executable +path. The cleanup identity needs permission to delete the complete test package; +if that stronger cleanup permission is deliberately separated from the push +identity, supply the corresponding job-scoped `GITHUB_TOKEN` instead of reusing +`REGISTRY_TEST_TOKEN`. + +Use a project-controlled namespace with a cleanup/expiry policy. An explicitly +selected disposable service such as `ttl.sh` may be used as a fallback test +endpoint only after its availability, trusted TLS, retention, and Cosign +behavior are accepted for that run; it is not a silent default or production +recommendation. The harness generates ephemeral signing material within Dagger, +uses no host Docker/Podman CLI or socket, retries only bounded readiness/read +probes, and never automatically retries the mutating Package flow. After a +successful Package it exports the immutable digest with a bounded read retry, +checks the bundle, image archive, logs, evidence, manifest, and result for +credential sentinels, and invokes raw `deploy-release` to prove the exact digest +handoff. A transport failure after publication may have begun is an ambiguous +partial outcome that requires registry inspection and cleanup. + +## GitHub Container Registry + +**Test status:** syntax-reviewed. Use GHCR as the project-controlled live +release-gate endpoint, and record the gate result for the exact release +candidate. Do not infer continuous vendor coverage from unit tests. + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + ghcr: + kind: oci_registry + registry: ghcr.io + repository_prefix: acme/rush-delivery-images + username_env: RD_OCI_GHCR_USERNAME + token_env: RD_OCI_GHCR_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +Replace `acme` with the lowercase user or organization namespace. The resulting +example package is +`ghcr.io/acme/rush-delivery-images/control-plane-api`. + +### Repository and publisher identity + +GitHub recommends the job-scoped `GITHUB_TOKEN` for a workflow publishing a +package associated with its own repository. Set job permissions to +`contents: write` and `packages: write` for the composed live workflow, and map: + +```yaml +permissions: + contents: write + packages: write + +steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + application-image-provider: ghcr + docker-socket: "" + dry-run: "false" + deploy-env: | + RD_OCI_GHCR_USERNAME=${{ github.actor }} + RD_OCI_GHCR_TOKEN=${{ github.token }} + RD_OCI_COSIGN_PRIVATE_KEY=${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD=${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY=${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} +``` + +`contents: write` is used by the successful composed workflow to move its +deploy tag; a raw Package-only job can keep `contents: read`. Neither setting is +needed by the GHCR protocol itself. + +GitHub documents `GITHUB_TOKEN` publication, classic PAT scopes, and package +linking in [Working with the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). +Rush Delivery adds `org.opencontainers.image.source` when a source repository +URL is supplied. Confirm after the first push that the package is connected to +the intended repository and that Actions access is inherited or explicitly +granted. GitHub's +[package access guide](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility) +distinguishes Read, Write, and Admin package roles. + +A repository that publishes the package through a workflow, or is explicitly +connected to the package, normally receives package Admin access. A job-scoped +`GITHUB_TOKEN` can therefore delete or restore package versions through GitHub's +preview REST API when that repository retains Admin access; `packages: write` +is not a provider-enforced separation from cleanup. Put any cleanup call in a +different, approved workflow and protected environment. A classic PAT without +`delete:packages` gives stronger token-scope separation, but classic package +scopes are not restricted to one package, repository prefix, or namespace; +effective access must also be constrained through the dedicated account, +organization, package, SSO, and Actions access policies. + +If the built-in job token cannot target the required namespace, use a dedicated +classic PAT with `write:packages` (which includes read) and authorize SSO when +required. Do not grant `delete:packages` to the publisher unless the same job is +explicitly responsible for cleanup. Keep package administration and live +publishing out of untrusted PR jobs. + +### GHCR pull, retention, and cleanup + +Public GHCR packages can be pulled anonymously. For private/internal packages, +grant the deployment repository or platform identity Read access and use a +pull-only token; never reuse the publishing token as runtime pull identity. +Public visibility also exposes the image's registry-hosted Cosign signature and +attestation package versions: +classify the SPDX dependency inventory and provenance source/build parameters +before choosing anonymous pull access. +Cloud Run does not accept that GHCR token through its runtime service account: +direct GHCR deployment is for public images, while private GHCR must be exposed +through an authenticated Artifact Registry remote repository. Google documents +this restriction in +[Deploying container images to Cloud Run](https://docs.cloud.google.com/run/docs/deploying) +and lists `https://ghcr.io` as a supported +[custom remote-repository upstream](https://docs.cloud.google.com/artifact-registry/docs/repositories/remote-overview#custom_urls). + +The deterministic SHA tag is navigation only. Retain the digest and Cosign +objects for every deployable release. Cleanup requires package Admin access; +GitHub documents package/version removal in +[Deleting and restoring a package](https://docs.github.com/en/packages/learn-github-packages/deleting-and-restoring-a-package). +Inventory the subject plus every tagged and untagged signature/attestation +package version before deleting a failed subject, and verify the package list +afterward. GitHub's Actions delete/restore API +support is documented as preview, so do not make recovery depend on an +unverified automatic deletion workflow. + +## Google Artifact Registry + +**Test status:** syntax-reviewed against current Google Cloud documentation; no +continuous live Rush Delivery test is claimed. + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + gar: + kind: oci_registry + registry: us-central1-docker.pkg.dev + repository_prefix: acme-production/rush-delivery-images + username_env: RD_OCI_GAR_USERNAME + token_env: RD_OCI_GAR_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +Here `acme-production` is the Google Cloud project ID and +`rush-delivery-images` is an Artifact Registry Docker repository. Google +documents the full +[`LOCATION-docker.pkg.dev/PROJECT/REPOSITORY` naming form](https://docs.cloud.google.com/artifact-registry/docs/docker/names). + +### Repository and permissions + +Create the Docker repository before publishing: + +```sh +gcloud artifacts repositories create rush-delivery-images \ + --project=acme-production \ + --location=us-central1 \ + --repository-format=docker \ + --description='Rush Delivery application images' +``` + +The current command and optional immutable-tag behavior are documented in +[Create standard repositories](https://cloud.google.com/artifact-registry/docs/repositories/create-repos). +Grant the publishing service account `roles/artifactregistry.writer` on this +repository. Package needs both push and read access for Cosign verification; +the Writer role supplies repository read/write capability. Use +`roles/artifactregistry.reader` for the deployment platform. Reserve +`roles/artifactregistry.repoAdmin` or a narrower custom delete role for cleanup; +Google's [image management guide](https://cloud.google.com/artifact-registry/docs/docker/manage-images) +separates tag/upload permissions from delete permissions. + +The predefined Writer role is not a strict no-delete publisher role: Google's +[current role matrix](https://cloud.google.com/iam/docs/roles-permissions/artifactregistry) +includes broader deletion authority, including +`artifactregistry.attachments.delete`. Rush Delivery's tag-addressed Cosign +objects are ordinary OCI image artifacts and do not exercise the separate GAR +Attachment resource API; removing that one unrelated permission is therefore +not a complete no-delete policy. If publisher deletion must be prohibited, +create and live-test a custom repository role containing only the required +manifest, blob, and tag operations. Re-run acceptance whenever Google or Cosign +changes its registry operations. + +Decide whether to enable immutable tags before the first release. Rush Delivery +uses the deterministic `sha-` navigation tag. With Artifact +Registry tag immutability, that tag can never move to a different digest, so a +same-source rebuild that produces different bytes is rejected even though the +subject itself is digest-addressed. Tagged versions in an immutable-tag +repository also cannot be deleted by cleanup policy. Treat the existing subject +as the release for that SHA, or use a separately governed repository/versioning +strategy for a replacement; validate retry and retention behavior in +pre-production. See Google's +[immutable-tag push contract](https://cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling#tagging) +and +[cleanup tag-state rules](https://cloud.google.com/artifact-registry/docs/repositories/cleanup-policy#tag-state). + +### Short-lived authentication + +Prefer Workload Identity Federation and a short-lived access token over a +service-account key. Configure the pool provider's attribute mapping and CEL +condition to admit only the exact GitHub organization, repository, and +protected `production` environment, then grant that exact federated principal +`roles/iam.workloadIdentityUser` on the dedicated publisher service account. +Do not grant the whole pool. Google documents the required principal binding and +attribute restrictions in +[Workload Identity Federation](https://docs.cloud.google.com/iam/docs/workload-identity-federation) +and its +[federation security guidance](https://docs.cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation). +For the placeholders below, the provider condition must be equivalent to +`assertion.repository == 'acme/control-plane' && assertion.environment == 'production'`; +map both claims explicitly and keep the GitHub environment protected. + +The following job exchanges the protected GitHub OIDC assertion directly for a +one-hour service-account access token and maps only that output to Rush +Delivery. The full action SHAs are the reviewed `actions/checkout@v5` and +`google-github-actions/auth@v3` revisions; retain the version comments when a +dependency updater replaces a SHA. + +```yaml +jobs: + publish: + runs-on: ubuntu-latest + environment: production + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + + - id: google-auth + name: Exchange GitHub OIDC for a GAR access token + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: rush-delivery-publisher@acme-production.iam.gserviceaccount.com + token_format: access_token + access_token_lifetime: 3600s + create_credentials_file: false + export_environment_variables: false + + - name: Publish with Rush Delivery + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + application-image-provider: gar + docker-socket: "" + dry-run: "false" + deploy-env: | + RD_OCI_GAR_USERNAME=oauth2accesstoken + RD_OCI_GAR_TOKEN=${{ steps.google-auth.outputs.access_token }} + RD_OCI_COSIGN_PRIVATE_KEY=${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD=${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY=${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} +``` + +The OIDC permission only allows GitHub to request an assertion; the pool +condition, service-account binding, and repository IAM role determine what it +can become and modify. `contents: write` is for the composed Rush Delivery +workflow's successful deploy-tag update, not GAR authentication; a raw +Package-only job can keep `contents: read`. The auth action is configured not to +create a credentials file or export ambient Google variables because Rush +Delivery needs only the explicit token output. The action's +[access-token contract](https://github.com/google-github-actions/auth#generating-oauth-20-access-tokens) +documents `token_format`, lifetime, and output behavior. + +Google's +[Artifact Registry authentication guide](https://docs.cloud.google.com/artifact-registry/docs/docker/authentication) +states that `oauth2accesstoken` tokens are valid for 60 minutes. The same guide +also supports `_json_key` with raw service-account JSON and +`_json_key_base64` with base64 JSON. Those are long-lived fallbacks: for Rush +Delivery's one-line env file, use a single-line/minified raw JSON value or the +base64 form, and protect it as a high-risk secret. Do not use a credential helper +as a substitute for the five provider values; Rush Delivery does not read the +host Docker configuration. + +### GAR pull, retention, and cleanup + +Grant each deployment platform the exact Artifact Registry read capability it +actually uses. For Cloud Run, the deployer needs Artifact Registry Reader and +the Cloud Run service agent must be able to read the image repository; the +service account selected with `--service-account` is the running application's +service identity, not its image-import credential. For GKE kubelet image pulls, +grant Artifact Registry Reader to the node service account or configure an +`imagePullSecret` with a separate reader credential; Workload Identity +Federation for GKE does not provide image-pull credentials. Package credentials +never reach either platform. See Google's +[Cloud Run deployment roles](https://docs.cloud.google.com/run/docs/deploying#required_roles) +and +[service identity model](https://docs.cloud.google.com/run/docs/securing/service-identity), +plus the +[GKE Artifact Registry integration](https://docs.cloud.google.com/artifact-registry/docs/integrate-gke) +and +[GKE image-pull troubleshooting contract](https://docs.cloud.google.com/kubernetes-engine/docs/troubleshooting/image-pulls). + +Apply cleanup policies only after a dry-run review and keep every production or +rollback digest plus associated Cosign objects. Google documents rule order, +keep precedence, and asynchronous application in the +[cleanup policy overview](https://docs.cloud.google.com/artifact-registry/docs/repositories/cleanup-policy-overview). +For a partial publication, list the subject, attachment tags, and every tagged +or untagged related package version, then use the provider's digest deletion +command under a cleanup identity. Verify the complete remaining inventory after +deletion instead of assuming a tag cleanup removed everything. + +## Amazon Elastic Container Registry + +**Test status:** syntax-reviewed against current AWS documentation; no +continuous live Rush Delivery test is claimed. + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + ecr: + kind: oci_registry + registry: 111122223333.dkr.ecr.us-east-1.amazonaws.com + repository_prefix: rush-delivery + username_env: RD_OCI_ECR_USERNAME + token_env: RD_OCI_ECR_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +For `artifact.image: control-plane-api`, create the exact ECR repository +`rush-delivery/control-plane-api`: + +```sh +aws ecr create-repository \ + --region us-east-1 \ + --repository-name rush-delivery/control-plane-api +``` + +Repeat for every distinct image suffix. Do not expect Package to create an ECR +repository. + +### IAM and short-lived authentication + +Use GitHub OIDC or another federation path to assume a release-specific IAM +role instead of storing long-lived AWS access keys. AWS recommends limiting the +GitHub OIDC trust policy to the intended organization, repository, and protected +branch/environment in +[Create a role for OIDC federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html). + +For a job protected by the GitHub `production` environment, use the environment +subject form and the standard STS audience in the role trust policy. Replace the +account, organization, and repository placeholders; do not widen `sub` to an +organization or wildcard repository: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", + "token.actions.githubusercontent.com:sub": "repo:acme/control-plane:environment:production" + } + } + } + ] +} +``` + +The corresponding job must request `id-token: write` and declare that exact +environment. This full action SHA is the reviewed +`aws-actions/configure-aws-credentials@v6.2.3` revision: + +```yaml +jobs: + package: + runs-on: ubuntu-latest + environment: production + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + + - name: Assume the ECR publisher role + uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 + with: + role-to-assume: arn:aws:iam::111122223333:role/rush-delivery-publisher + aws-region: us-east-1 +``` + +AWS documents the `aud`/`sub` checks and the environment subject form in its +[GitHub OIDC condition-key guidance](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif) +and the action documents the required +[OIDC workflow permission](https://github.com/aws-actions/configure-aws-credentials#oidc). + +The publisher needs `ecr:GetAuthorizationToken` plus repository-scoped upload +and verification-read actions. AWS's +[least-privilege push example](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-push-iam.html) +lists `BatchCheckLayerAvailability`, `BatchGetImage`, +`CompleteLayerUpload`, `InitiateLayerUpload`, `PutImage`, and +`UploadLayerPart`. Include `GetDownloadUrlForLayer` for verification reads. +Scope repository actions to every exact `rush-delivery/` ARN. Put delete +actions in a separate cleanup role. + +After assuming the role, generate the ECR password immediately before Package: + +```sh +ecr_token="$(aws ecr get-login-password --region us-east-1)" + +readonly ecr_env="$RUNNER_TEMP/rush-delivery-ecr.env" +umask 077 +trap 'rm -f -- "$ecr_env"' EXIT +{ + printf 'RD_OCI_ECR_USERNAME=AWS\n' + printf 'RD_OCI_ECR_TOKEN=%s\n' "$ecr_token" + printf 'RD_OCI_COSIGN_PRIVATE_KEY=%s\n' "$RD_OCI_COSIGN_PRIVATE_KEY" + printf 'RD_OCI_COSIGN_PASSWORD=%s\n' "$RD_OCI_COSIGN_PASSWORD" + printf 'RD_OCI_COSIGN_PUBLIC_KEY=%s\n' "$RD_OCI_COSIGN_PUBLIC_KEY" +} >"$ecr_env" +unset ecr_token +``` + +Pass `$ecr_env` as `deploy-env-file` and delete it in an exit trap. AWS's +[ECR authentication guide](https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html) +requires username `AWS`, says the token inherits the IAM principal's scope, and +documents a 12-hour lifetime. `docker login` in that guide explains the wire +credentials; Rush Delivery passes them directly and does not require the login +command. + +### ECR pull, retention, and cleanup + +Give the runtime role only ECR authorization and pull operations for the exact +repository. Keep it distinct from the publishing role. + +Preview lifecycle policies before activation and ensure they retain the release +and rollback subjects plus the digest-derived `.sig` and `.att` image tags. AWS +documents that lifecycle actions are asynchronous in +[Creating a lifecycle policy](https://docs.aws.amazon.com/AmazonECR/latest/userguide/lp_creation.html). +ECR's lifecycle behavior for OCI reference artifacts does not describe this +`v0.9.0` storage mode; Rush Delivery does not publish those reference artifacts. +Treat the subject and both tag-addressed Cosign attachments as separately +retained OCI image content. + +For partial publication, use a cleanup role to inventory the subject, +attachment tags, and untagged image history, delete only the reviewed targets in +the provider-required order, and verify the complete repository inventory +afterward. Rush Delivery does not automate deletion. + +## Docker Hub + +**Test status:** syntax-reviewed against current Docker and Dagger +documentation; no continuous live Rush Delivery test is claimed. + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + dockerhub: + kind: oci_registry + registry: docker.io + repository_prefix: acme + username_env: RD_OCI_DOCKERHUB_USERNAME + token_env: RD_OCI_DOCKERHUB_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +For `artifact.image: control-plane-api`, the repository is +`docker.io/acme/control-plane-api`. Use a single-segment image suffix for this +recipe and create `acme/control-plane-api` before publishing. Docker documents +repository naming and visibility in +[Create a repository](https://docs.docker.com/docker-hub/repos/create/), while +Dagger's [container publishing recipe](https://docs.dagger.io/cookbook/containers/) +uses the `docker.io` registry authority. + +### Publisher identity and CI mapping + +For a personal access token (PAT), set `RD_OCI_DOCKERHUB_USERNAME` to the +personal Docker ID that owns the token. Give a dedicated release identity a +time-bounded Write PAT and only the account/team access needed to push the +chosen personal or organization repository. Docker PAT permissions are Read, +Write, or Delete; the PAT itself is not repository-scoped. + +For an organization access token (OAT), set the username to the exact +organization name, not the user who created the token. On Docker Team or +Business, grant the OAT Image Push for only this repository. OATs offer +repository-level Image Pull/Image Push permissions but no image-delete +permission, and Docker documents that they are incompatible with Docker Desktop +and Image Access Management. Docker documents PAT creation and rotation in +[Personal access tokens](https://docs.docker.com/security/access-tokens/) and +repository-scoped organization tokens in +[Organization access tokens](https://docs.docker.com/enterprise/security/access-tokens/). + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + application-image-provider: dockerhub + docker-socket: "" + dry-run: "false" + deploy-env: | + RD_OCI_DOCKERHUB_USERNAME=${{ vars.RD_OCI_DOCKERHUB_USERNAME }} + RD_OCI_DOCKERHUB_TOKEN=${{ secrets.RD_OCI_DOCKERHUB_TOKEN }} + RD_OCI_COSIGN_PRIVATE_KEY=${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD=${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY=${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} +``` + +Image Push must cover the subject and Cosign objects. An OAT cannot be the +cleanup credential: use a separately controlled organization owner/admin path +in Docker Home or the Hub API, or a dedicated personal administrative identity +with a Delete PAT when the repository access model permits it. Keep that +identity outside the publishing job and exercise deletion in a disposable +repository before relying on it. Do not use an account password and do not run +`docker login` for Rush Delivery. Keep the Docker Hub username non-secret; +Dagger may display it in registry-auth progress. + +### Docker Hub pull, retention, and cleanup + +Public repositories can be pulled without a secret but remain subject to +Docker Hub usage/rate policy. Private deployment should use a separate Read or +Image Pull token configured in the target platform. Pair a PAT with its +personal Docker ID or an OAT with its organization name exactly as above. +Docker documents pull identity and rate attribution in +[Docker Hub pull usage and limits](https://docs.docker.com/docker-hub/usage/pulls/). + +Retain the digest, deterministic tag, signatures, and attestations together. +Docker Hub supports OCI artifacts, but cleanup should be verified in the +repository's image/artifact view. Tag removal alone is not proof that an image +digest or every Cosign object was removed. Docker documents manual tag deletion +in [Tags on Docker Hub](https://docs.docker.com/docker-hub/repos/manage/hub-images/tags/) +and broader artifact support in +[Image management](https://docs.docker.com/docker-hub/repos/manage/hub-images/). + +## Common Post-Provisioning Test + +For every registry: + +1. Run `validate-metadata-contract`. +2. Run provider-off OCI dry run; confirm no registry, digest, or evidence is + emitted. +3. Run named-provider dry run without credentials; confirm the planned literal + repository. +4. In a protected disposable namespace, run one live Package and export the + returned directory. +5. Confirm the manifest reference is exactly `repository@digest`, all three + evidence hashes match, and no credential sentinel appears in output. +6. Use real Cosign verification to prove the subject signature and both + attestation predicates. For package-version inventory, require one subject + plus at least two non-subject versions and allow additional untagged history; + counts alone are not semantic proof. +7. Configure the deployment platform's separate pull identity and pull by the + manifest digest. +8. Exercise cleanup on disposable content, including an intentionally partial + subject/attachment package-version set. +9. Record the exact service/tier/region, policy, client versions, and date of the + successful test. Re-run after registry, Cosign, permissions, or retention + changes. + +When a live attempt fails, follow +[OCI application-image troubleshooting](../oci-application-image-troubleshooting) +before retrying. diff --git a/docs-versions/versioned_docs/version-v0.9.0/providers.md b/docs-versions/versioned_docs/version-v0.9.0/providers.md new file mode 100644 index 0000000..7bf1bac --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/providers.md @@ -0,0 +1,153 @@ +--- +id: "providers" +title: "Providers" +sidebar_label: "Providers" +--- + +Rush Delivery keeps provider behavior behind explicit adapters. Local use works +with providers off; CI can opt into adapters by passing provider names and +credentials. + +## Source Providers + +`sourceMode=local_copy` consumes a caller-provided `repo` Directory. For local +development, offline runs, and unpushed changes, use the versioned +`rush-delivery-local` launcher so bounded defaults and repository patterns are +applied to `host.directory` before transfer. The Action uses the same launcher +parser. The old top-level call path remains available as the `legacy` policy. +See [bounded local-copy imports](../local-copy-source-imports). + +`sourceMode=git` clones or fetches the source from provider-neutral coordinates. +This is the recommended CI path and does not require `--repo`: + +- `sourceRepositoryUrl` +- `sourceRef` +- `gitSha` +- `prBaseSha` when validating pull requests +- `sourceAuthTokenEnv` when private source access is required + +The token value is read from the deploy environment file, not printed in logs. + +## Toolchain Image Providers + +`toolchainImageProvider=off` builds toolchain containers inside the current +Dagger run. + +`toolchainImageProvider=github` uses GitHub Container Registry as an OCI image +store for content-addressed toolchain images. Image references are derived from +normalized runtime specs and provider metadata. + +Optional `.dagger/toolchains/rush.yaml` changes the Rush toolchain spec to v2 +and includes the digest-pinned base, platform, and every ordered checksummed +download in its cache identity. Projects without the file keep the exact v1 +identity. The [toolchain guide](../rush-toolchain) defines the safe extension +and update procedure. + +Policies: + +- `toolchainImagePolicy=lazy` keeps trusted workflow behavior unchanged: pull an + existing image, or build and publish a missing one. +- `toolchainImagePolicy=pull-or-build` pulls an existing image, or builds it + locally on miss without publishing. Use this for pull-request validation. + +## Rush Cache Providers + +`rushCacheProvider=off` keeps Rush install behavior local to the current Dagger +engine. + +`rushCacheProvider=github` stores a compressed Rush install cache archive in a +GHCR image. The cache reference is a stable project snapshot identified by the +`cache.version` value in `.dagger/rush-cache/providers.yaml`. Rush Delivery +restores that snapshot before `rush install`, lets Rush reconcile the +dependencies, and can publish the refreshed snapshot after the install +succeeds. + +Policies: + +- `rushCachePolicy=lazy` is for trusted workflows: restore the existing cache + when available, run Rush install, then publish the post-install cache. +- `rushCachePolicy=pull-or-build` is for pull-request validation: restore the + existing cache when available and run Rush install, but never publish a cache + from the PR run. + +## Application Image Providers + +`applicationImageProvider=off` preserves filesystem-only workflows and supports +credential-free OCI dry runs. A live selected OCI package target must name an +`oci_registry` provider from `.dagger/application-images/providers.yaml`. +When no selected package target is OCI, planning ignores the application-image +provider input, provider metadata file, and provider credentials entirely. + +The adapter is registry-neutral. Registry authority and repository prefix may +be static metadata or public values named by `registry_env` and +`repository_prefix_env`. Selected workflow values come from the +workflow-plus-deploy overlay; standalone Package uses `deploy-env`. The four +sensitive credential values and derived Docker configuration become Dagger +secrets; the globally unique `username_env` resolves to Dagger's required +non-secret registry username. All credential names are protected, and public +coordinate names cannot alias them or another framework capability. Named dry +runs resolve only required coordinates. Provider metadata and output never +contain credential values. + +Application images are distinct from toolchain images and Rush cache images. +They use package-target image names, are published once per target under a +source navigation tag, and are recorded and deployed only as verified digest +references. See [OCI application images](../oci-application-images). + +## Deploy Providers + +Deploy providers are target-level concerns. A target runtime decides what +environment variables, file mounts, static env values, workspace paths, and +tooling it needs through deploy target metadata. + +The framework only passes allowlisted data into each target runtime. + +Deploy-platform files should be passed through `runtimeFiles` and mounted from +target metadata. This keeps those credentials out of source acquisition, Rush +cache, package artifacts, toolchain image hashes, logs, and generated +manifests. OCI registry tokens and Cosign key material are not runtime files; +they are Package-only environment values selected by application-image +provider metadata. + +## CI Provider Responsibilities + +A CI provider should provide: + +- Dagger CLI availability. +- Source coordinates for Git source mode. +- A workflow environment file with shared source/provider values. +- A deploy environment file with deploy credentials and project settings. +- A release environment file with npm credentials when running package release + through `workflow` or `release-packages`. +- A runtime files directory for deploy-only credential or config files when + targets need file mounts. +- Optional Docker socket only for a project-owned legacy deploy target that + explicitly needs it. First-class OCI package artifacts build and publish + through Dagger and do not use a host Docker daemon or socket. +- Permissions for any selected provider adapters. + +For GitHub PR validation, `packages: read` is enough when both provider +policies are `pull-or-build`. Trusted release workflows that use `lazy` need +`packages: write` so refreshed artifacts can be published. + +Application image registry permissions are provider-specific. A live OCI +release needs push access for the selected registry identity. Pull access at +deployment belongs to the target platform identity, not the Rush Delivery +deploy script. + +The CI provider should not compute deploy plans, package artifacts, update +deploy tags, apply package versions, publish npm packages directly, or encode +target-specific behavior. Rush Delivery calls Rush for versioning and package +publishing when the `npm` release target is selected or when the standalone +`release-packages` entrypoint is called. + +The GitHub Action wrapper in this repository is the first CI adapter. It +prepares GitHub-specific defaults and then calls the same Dagger `workflow`, +`validate`, or `release-packages` entrypoints as raw CLI usage. For `workflow`, +it supports `workflow-env`, `deploy-env`, and `release-env` inputs. + +To adopt application images, work through the +[OCI application images tutorial](../tutorial/oci-application-images), +then use the [production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting). diff --git a/docs-versions/versioned_docs/version-v0.9.0/quick-start/ci-cli.md b/docs-versions/versioned_docs/version-v0.9.0/quick-start/ci-cli.md new file mode 100644 index 0000000..b6b2f8a --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/quick-start/ci-cli.md @@ -0,0 +1,144 @@ +--- +title: "CI Using Command Line" +sidebar_label: "CI Using Command Line" +description: "Call the Rush Delivery Dagger module directly from CI scripts." +--- + +Use the raw Dagger command when your CI provider is not GitHub Actions, or when +you want to own all surrounding shell steps yourself. + +This mode clones the target repository inside Dagger, so the CI runner does not +need to mount the repository into the module. + +For pull-request validation: + +```sh +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +DEPLOY_ENV_FILE="${RUNNER_TEMP}/dagger-validate.env" +SOURCE_REPOSITORY_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + +cat > "${DEPLOY_ENV_FILE}" < "${WORKFLOW_ENV_FILE}" < "${DEPLOY_ENV_FILE}" < "${RELEASE_ENV_FILE}" < "${RELEASE_ENV_FILE}" <gcp-credentials.json +``` + +Next, see [CI Using Command Line](../ci-cli) if you want to call the module +directly from a custom CI script. + +This is a filesystem-first release baseline. It omits the application-image +provider and needs no OCI registry or Cosign credentials. Existing +directory/archive projects keep the default `off` without adding +`.dagger/application-images` metadata. To add an OCI target, follow the +[OCI application images tutorial](../../tutorial/oci-application-images) +and set `docker-socket: ""` in OCI-only Action jobs. Use the +[production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting) before a +live release. + +## Package Release + +Use `release-targets-json: '["npm"]'` in the main workflow when package release +should share the same source acquisition, Rush install cache, and build +lifecycle as deploy. Deploy tags stay on the original source SHA; Rush package +release pushes its generated version commit to the configured target branch. + +Use `entrypoint: release-packages` when npm package release/versioning should +stay standalone. Keep npm credentials in `release-env`; deploy credentials stay +in `deploy-env`. + +NPM provenance is disabled by default; opt in from `.dagger/release/npm.yaml` +only when the Dagger release runtime is configured for supported npm provenance. + +This is the minimal package-only shape. It publishes to npmjs through Rush and +does not use GHCR-backed provider artifacts: + +```yaml +permissions: + contents: read + +jobs: + release-packages: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: release-packages + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +Use provider `github` and add `packages` permissions only when the repository +has Rush Delivery provider metadata for toolchain images or Rush install cache. + +Rush Delivery expects normal Rush release inputs in the repository: +`.dagger/release/npm.yaml`, `common/config/rush/.npmrc-publish`, Rush change +files, package `publishConfig`, and any Rush version policies referenced from +`rush.json`. + +For the broader docs map, start from the [Introduction](../../introduction). diff --git a/docs-versions/versioned_docs/version-v0.9.0/quick-start/local-run.md b/docs-versions/versioned_docs/version-v0.9.0/quick-start/local-run.md new file mode 100644 index 0000000..f8f44d8 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/quick-start/local-run.md @@ -0,0 +1,84 @@ +--- +title: "Local Runs" +sidebar_label: "Local Runs" +description: "Test unpushed changes from a local working tree." +--- + +For local testing, pass the working tree explicitly. This keeps unpushed edits +available to Dagger and avoids relying on a remote Git ref that does not contain +your latest changes. + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=manual \ + --force-targets-json='[]' \ + --environment=prod \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --application-image-provider=off +``` + +If the forced selection includes an OCI target, this dry run reports the +relative image and platform but does not build, scan, publish, sign, or resolve +credentials. Select a named application-image provider only when you also want +to validate its planned repository. + +For local PR-style validation only: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + validate \ + --event-name=pull_request \ + --pr-base-sha="$(git merge-base HEAD origin/main)" +``` + +For a local package-release dry-run: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + release-packages \ + --git-sha="$(git rev-parse HEAD)" \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +This reads `.dagger/release/npm.yaml`, runs the release build lifecycle, and +executes the non-publishing Rush publish path. It does not require `NPM_TOKEN`, +does not push the generated version commit, and does not publish packages. + +Avoid live package publishing from a local workstation unless you are +deliberately testing the release path with disposable packages. Live package +release expects Git source mode, release env credentials, and a clean CI-style +source ref. + +The launcher applies bounded source exclusions before Dagger uploads the +worktree. Install and verify the release asset, review defaults, and add narrow +inclusions through `.dagger/source-import.ignore` by following the +[bounded local-copy guide](../../local-copy-source-imports). Direct top-level +`dagger call ... --source-mode=local_copy` remains the legacy-compatible path, +but cannot honor repository-controlled pre-import inclusions. + +Keep live deploy credentials out of source. If a local live deploy needs files +such as cloud credentials, pass them through a runtime files directory and refer +to them from target metadata. + +For deployment and release metadata, see [Metadata contracts](../../metadata). +For workflow shape and release behavior, see the [Workflow Guide](../../workflows). +For OCI-specific local planning and live rollout, use the +[OCI application images tutorial](../../tutorial/oci-application-images), +[production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting). diff --git a/docs-versions/versioned_docs/version-v0.9.0/rush-toolchain.md b/docs-versions/versioned_docs/version-v0.9.0/rush-toolchain.md new file mode 100644 index 0000000..a0d87fd --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/rush-toolchain.md @@ -0,0 +1,131 @@ +--- +id: "rush-toolchain" +title: "Project-Owned Rush Toolchain" +sidebar_label: "Project-Owned Rush Toolchain" +description: "Add digest-pinned, checksummed tools to the shared Rush image." +--- + +Rush Delivery `v0.9.0` lets a repository add deterministic executables to the +shared Rush workflow image through `.dagger/toolchains/rush.yaml`. The contract +is intentionally narrow: immutable base image, checksummed HTTPS downloads, and +fixed executable destinations. It is not a general container build script. + +Projects without this file keep the exact existing Node-only toolchain spec, +hash, provider cache reference, and provider-off behavior. + +## Contract + +Use the exact versioned +[`rush-toolchain` schema](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/rush-toolchain.schema.json). The same +metadata is available as a tested +[configuration fragment](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/deployment-environment-compatibility/rush-toolchain.yaml): + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-toolchain.schema.json +version: rush-delivery-rush-toolchain/v1 +base_image: node:24-bookworm-slim@sha256:65932751ed4073ed02f5c04e494e4b2572a891b7dbea0568a863dc80341bf848 +platform: linux/amd64 +downloads: + - url: https://github.com/astral-sh/uv/releases/download/0.12.2/uv-x86_64-unknown-linux-gnu.tar.gz + sha256: d66e96b5f1ca3b99806eee283a8125d33a0bd669e6e6d9bc4ab7ffda63c41bf4 + format: tar_gz + archive_path: uv-x86_64-unknown-linux-gnu/uv + destination: /usr/local/bin/uv + mode: "0755" +``` + +The configured base must be digest-pinned Linux/amd64 and provide Bash, +Node.js 24, `apt-get`, and the Debian behavior needed by the standard Rush +bootstrap. The supported pattern is a pinned `node:24-bookworm-slim` image. +Rush Delivery runs capability/version preflight before contacting project +download URLs. + +Each of 1–16 ordered downloads has: + +- an HTTPS URL without userinfo, query, fragment, credentials, or interpolation; +- a lowercase SHA-256 digest; +- `raw` or `tar_gz` format; +- one normalized `archive_path` exactly when using `tar_gz`; +- a unique direct child of `/usr/local/bin` as its destination; and +- executable mode fixed to the string `"0755"`. + +See the schema for the exact syntax. Unknown fields are rejected. Shells, +commands, environment maps, package-manager hooks, arbitrary destinations, and +secret injection are not part of this metadata version. + +## Download And Extraction Guarantees + +Rush Delivery transfers each URL in a framework-owned, digest-pinned helper +container. It enforces HTTPS on the first request and every redirect, at most +five redirects, a 30-second connection timeout, a 300-second total timeout, and +a 256 MiB compressed-byte limit. + +The declared SHA-256 is verified before extraction or installation. For a tar +archive, the named member must occur exactly once, be a regular file rather +than a link, and declare no more than 256 MiB before extraction. The extracted +file is checked again for regular-file/link status and size, then copied to its +fixed destination with mode `0755`. + +Download data never becomes a module-process string. No workflow/deploy host +environment, runtime file, application-image provider value, or arbitrary +secret is supplied to project toolchain construction. + +## Lifecycle And Cache Identity + +Configured metadata produces `rush-delivery-toolchain-image/v2`. The toolchain +image hash includes metadata version, pinned base, platform, and every field of +every download in order, plus the framework's fixed Rush bootstrap. Reordering +downloads or changing any checksum changes the cache reference. + +The installed tools are available before Rush install, Detect, Build, +validation, Rush-requiring Package work, and npm package Release. OCI-only +Package work does not construct a Rush toolchain it does not use. + +Provider behavior remains the same: + +- provider `off` builds the configured image in the current Dagger run; +- `github` with `lazy` pulls by spec hash, builds on miss, and publishes after a + trusted successful path; and +- `github` with `pull-or-build` pulls or builds locally without publishing. + +Toolchain-provider registry credentials remain explicit framework capabilities. +They do not become toolchain environment variables and are never hashed. + +## Safe Update Procedure + +1. Select the upstream release and `linux/amd64` asset from an authenticated + operator workstation. +2. Download the asset independently and calculate SHA-256. Do not copy a digest + from an untrusted mirror or from the same transport without verification. +3. For `tar_gz`, list the archive and record the exact regular-file member. +4. Update URL, checksum, archive path, and destination together in a reviewed + change. +5. Validate metadata before a build: + + ```sh + dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call validate-metadata-contract --repo=. + ``` + +6. Run a provider-off validation and assert the tool's version before the first + package command that needs it. +7. In a trusted non-production run, use the normal `lazy` toolchain provider to + populate the new content-addressed cache. PR jobs can then use + `pull-or-build` without write permission. +8. Promote the same reviewed metadata to production. Do not copy a cache tag + between different metadata hashes. + +## Failure Guide + +| Failure | Meaning | Operator action | +| ------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Base preflight fails | The selected base is outside the Node 24 Debian contract | Choose a supported digest-pinned base; do not add arbitrary bootstrap commands | +| HTTPS/redirect/timeout/size failure | Transfer did not satisfy framework bounds | Verify upstream availability and asset size; mirror only under an approved HTTPS origin and update the URL/checksum together | +| Checksum mismatch | Downloaded bytes are not the reviewed asset | Stop; inspect upstream provenance or compromise before changing the digest | +| Member missing/duplicated/not regular | Archive layout or type is unsafe or changed | Inspect the new archive independently and update to one exact regular member | +| Tool missing in Rush scripts | Destination or selected metadata is wrong | Validate `.dagger/toolchains/rush.yaml` and run provider off to avoid a stale provider diagnosis | +| Provider auth error | Cache registry capability failed | Fix provider credentials/permissions; do not treat auth failure as a cache miss | + +Follow the [mixed Node/Python tutorial](../tutorial/mixed-node-python-toolchain) +for a complete first rollout and the [upgrade guide](../upgrade-v0-9-0) for +compatibility and recovery. diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial.md new file mode 100644 index 0000000..3bdf1dc --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial.md @@ -0,0 +1,116 @@ +--- +id: "tutorial" +title: "Tutorial" +sidebar_label: "Tutorial" +description: "Build a Rush Delivery setup from Rush metadata to deployment." +--- + +This tutorial walks through a complete Rush Delivery setup for a Rush monorepo. +It uses two real public projects as worked examples: + +[BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk) + +is the deployment example. It is wired end to end with Rush Delivery, deploys a +NestJS backend to Google Cloud Run, deploys a React webapp to Cloudflare Pages, +and validates pull requests before changes reach `main`. + +[BootstrapLaboratory/labkit](https://github.com/BootstrapLaboratory/labkit) + +is the package release example. It publishes public npm packages through Rush +Delivery `release-packages`, Rush change files, `.dagger/release/npm.yaml`, and +a dedicated GitHub workflow. + +The cloud providers are examples, not requirements. The reusable part is the +shape: + +- Rush owns project identity, dependency graph, selected project commands, and + deploy bundles. +- `.dagger` metadata describes deployment targets, package artifacts, + validation targets, provider-backed cache, and provider-backed toolchains. +- GitHub Actions stays thin. It authenticates to external systems, passes env + and runtime files, and calls the Rush Delivery action. +- Rush Delivery owns source acquisition, affected target detection, build, + package, validation, deploy ordering, and runtime execution. + +## What You Will Build + +By the end of the tutorial, a project should have this shape: + +```text +. +├── rush.json +├── common/config/rush/ +├── .dagger/ +│ ├── deploy/ +│ ├── package/ +│ ├── release/ +│ ├── rush-cache/ +│ ├── toolchains/ +│ ├── toolchain-images/ +│ ├── validate/ +│ └── source-import.ignore +└── .github/workflows/ +``` + +The tutorial does not teach NestJS, Relay, React, Google Cloud Run, Cloudflare +Pages, or npm package design in depth. Those are implementation details of the +example projects. The point is to teach how a Rush project becomes a Rush +Delivery project. + +## Chapters + +1. [Rush Monorepo Baseline](rush-monorepo-baseline) +2. [Rush Commands](rush-commands) +3. [Dagger Metadata Map](dagger-metadata-map) +4. [Provider Artifacts](provider-artifacts) +5. [Package Targets](package-targets) +6. [Deploy Mesh](deploy-mesh) +7. [Deploy Targets](deploy-targets) +8. [Validation Targets](validation-targets) +9. [GitHub Actions](github-actions) +10. [Local Dry Runs](local-dry-runs) +11. [Adapt To Your Project](adapting-to-your-project) +12. [NPM Package Release Baseline](npm-package-release-baseline) +13. [Release Metadata](release-metadata) +14. [Package Release Workflow](package-release-workflow) +15. [Mixed Node/Python Toolchain](mixed-node-python-toolchain) + +For an opt-in, production-oriented OCI image path, continue with the +[OCI Application Images tutorial](oci-application-images). It starts +from a minimal Rush repository and covers credential-free planning, GHCR and +Cosign bootstrap, digest-only deployment, GitHub Actions, split-stage handoff, +rollback, and environment-selected registry profiles. + +## The Deployment Example Repository + +The deployment example has three Rush projects: + +- `api-contract` in `libs/api` +- `server` in `apps/server` +- `webapp` in `apps/webapp` + +Its deployment model has two deploy targets: + +- `server`, packaged with `rush deploy` and deployed by a Cloud Run script +- `webapp`, packaged as a static build directory and deployed by a Cloudflare + Pages script + +Its validation model includes normal Rush validation plus a backend runtime +validation target that starts Postgres, Redis, runs migrations, starts the +server, and performs a smoke check. + +Use the example repository as a reference implementation. Copy shapes and +contracts from it, but adapt target names, scripts, environment variables, and +provider choices to your own product. + +## The Package Release Example Repository + +The package release example is +[BootstrapLaboratory/labkit](https://github.com/BootstrapLaboratory/labkit). It +has a package-only Rush monorepo with public npm packages, Rush version +policies, package `publishConfig`, `.npmrc-publish`, and a +`release-packages` workflow using Rush Delivery `v0.7.0`. + +Use LabKit as the reference for npm publishing shape. Copy the release +contracts from it, but adapt package names, version policy names, registry, +access level, and token handling to your own packages. diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/adapting-to-your-project.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/adapting-to-your-project.md new file mode 100644 index 0000000..35f729e --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/adapting-to-your-project.md @@ -0,0 +1,153 @@ +--- +title: "Adapt To Your Project" +sidebar_label: "Adapt To Your Project" +--- + +The example repository is intentionally concrete, but your project does not +need to copy its cloud providers or application stack. Copy the contracts and +adapt the details. + +## Choose Target Names + +Start with the deployable things in your product: + +- `api` +- `worker` +- `webapp` +- `admin` +- `docs` +- `migrations` + +Use the same names consistently across `.dagger/deploy`, +`.dagger/package`, and `.dagger/validate` where those targets exist. + +## Choose Package Shapes + +Use `rush_deploy_archive` when the target needs a runtime bundle with package +dependencies. Backend services often fit this shape. + +Use `directory` when the target already builds to a deployable directory. +Static sites and frontend assets often fit this shape. + +Use `oci_image` when a deployment platform consumes a container image. Define +one build context, Dockerfile, image name, platform, and scan policy in the +package target. Add `.dagger/application-images/providers.yaml` only when a +named provider is needed for planned or live OCI publication; filesystem-only +projects omit it. + +## Choose Deploy Scripts + +Rush Delivery is provider-neutral. A deploy script can call: + +- `gcloud` +- `wrangler` +- `kubectl` +- `helm` +- `aws` +- `az` +- an internal deployment CLI + +Keep cloud-specific logic in the script and runtime metadata. Keep the Rush +Delivery metadata shape the same. + +## Choose Runtime Files + +Runtime files are for deploy-only file inputs: + +- cloud credentials +- kubeconfig +- service account JSON +- generated deployment certificates + +Do not commit those files. Prepare them in CI and pass them with +`runtime-file-map`. + +Do not use runtime files for OCI registry tokens, Cosign private keys, signing +passwords, or Cosign public keys. Those are Package-only environment values +whose names come from application-image provider metadata. + +## Choose Validation Depth + +Start with Rush commands: + +- `verify` +- `lint` +- `test` +- `build` + +Add validation targets only when you need service orchestration. A database, +message broker, long-running server, or smoke check is a good reason. + +## Common Mistakes + +Mismatched target names: + +- The service mesh says `api`. +- The package target file says `server`. +- The deploy target file says `backend`. + +Pick one name and use it everywhere. + +Stale Rush install cache: + +- Rush Delivery restores the configured cache snapshot and then runs + `rush install`, so normal lockfile and package changes should be reconciled by + Rush. +- If you intentionally want to discard the existing install snapshot, bump + `cache.version` in `.dagger/rush-cache/providers.yaml`. + +Publishing from PRs: + +- PR workflows should use `packages: read` and `pull-or-build`. +- Trusted release workflows can use `packages: write` and default `lazy`. + +Credential files in source: + +- Use runtime files instead. +- Mount them only into targets that need them. + +OCI credentials in build or deploy metadata: + +- Keep every application-image provider credential name out of package build + and deploy runtime env declarations. +- Supply those values only through workflow/deploy environment input; Rush + Delivery converts them to Package-only Dagger secrets. + +Deploy scripts depending on the whole repo: + +- Prefer narrow runtime workspaces. +- Add only the dirs and files the script truly needs. + +Package release mixed into deploy metadata: + +- Keep npm package release in `.dagger/release/npm.yaml`. +- Keep deploy targets in `.dagger/deploy` and `.dagger/package`. +- Use `release-env` for npm credentials and `deploy-env` for deploy/build + inputs. + +## Final Checklist + +- Rush projects are stable and buildable. +- Rush commands cover validation and build. +- `.dagger/package` defines deploy artifacts and any build-time env allowlists. +- `.dagger/deploy` defines deploy ordering and runtime behavior. +- `.dagger/application-images` defines registry/Cosign settings only when OCI + application artifacts are adopted. +- `.dagger/release` defines npm package release behavior when the repository + publishes packages. +- `.dagger/validate` defines only orchestration-heavy checks. +- Only selected provider adapters have matching metadata and CI permissions. +- PR and release workflows use different permissions and policies. +- Local dry-runs work before live deployment. + +Next: [NPM Package Release Baseline](../npm-package-release-baseline). + +For editor validation, point metadata files at exact published schema versions +such as +`https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-target.schema.json`. + +For an OCI project shape, continue with the +[OCI application images tutorial](../oci-application-images), +[production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/dagger-metadata-map.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/dagger-metadata-map.md new file mode 100644 index 0000000..09d1d1d --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/dagger-metadata-map.md @@ -0,0 +1,120 @@ +--- +title: "Dagger Metadata Map" +sidebar_label: "Dagger Metadata Map" +--- + +Rush Delivery is configured by metadata in `.dagger`. The directory belongs to +the product repository, not to the Rush Delivery module, because target +behavior is product-specific. + +A full Rush Delivery repository can use this layout: + +```text +.dagger/ +├── application-images/ +│ ├── grype.yaml +│ └── providers.yaml +├── deploy/ +│ ├── services-mesh.yaml +│ └── targets/ +│ ├── server.yaml +│ └── webapp.yaml +├── package/ +│ └── targets/ +│ ├── server.yaml +│ └── webapp.yaml +├── release/ +│ └── npm.yaml +├── rush-cache/ +│ └── providers.yaml +├── toolchain-images/ +│ └── providers.yaml +└── validate/ + └── targets/ + └── server.yaml +``` + +Each part answers one question. + +Deploy-only repositories can omit `.dagger/release`. Package-only repositories +can omit `.dagger/deploy` and `.dagger/package`. + +## Deployment Graph + +`.dagger/deploy/services-mesh.yaml` answers: + +- Which deploy targets exist? +- Which targets must run before other targets? + +The example says `webapp` waits for `server`. + +## Package Targets + +`.dagger/package/targets/*.yaml` answers: + +- What artifact does this target need? +- Is the artifact a Rush deploy archive, a built directory, or an OCI image? +- Which Rush project and deploy scenario produce it? + +## Application Images + +`.dagger/application-images/providers.yaml` answers: + +- Which registry namespace receives application images? +- Which environment variable names supply registry and key-backed Cosign + credentials? + +An OCI package target can also reference +`.dagger/application-images/grype.yaml` as its explicit scanner ignore policy. +Filesystem-only repositories omit this directory. Provider `off` remains the +default and requires no application-image metadata. + +## Deploy Targets + +`.dagger/deploy/targets/*.yaml` answers: + +- What runtime image should execute the deploy? +- Which files from the built workspace should be mounted? +- Which tools should be installed into the deploy runtime? +- Which environment variables may be passed? +- Which deploy script should run? + +## Provider Artifacts + +`.dagger/toolchain-images/providers.yaml` and +`.dagger/rush-cache/providers.yaml` answer: + +- Where should reusable toolchain images and Rush install cache be stored? +- Which environment variables provide repository, token, and username? +- Which Rush install paths should be restored and published? + +## Package Release + +`.dagger/release/npm.yaml` answers: + +- Which package release strategy should Rush Delivery use? +- Which target branch receives Rush version commits? +- Which npm registry, tag, access level, and token env should be used? + +Package release metadata stays separate from deploy target metadata. Rush still +decides which packages are publishable and how change files affect versions. + +## Validation Targets + +`.dagger/validate/targets/*.yaml` answers: + +- Which additional runtime services are needed for validation? +- Which commands or long-running services should start? +- Which smoke checks should prove the target works? + +## Checklist + +- Keep `.dagger` metadata small and declarative. +- Put provider credentials in CI env, not in metadata. +- Keep OCI registry and Cosign credentials in the Package environment; never + place them in runtime files or deploy runtime allowlists. +- Put npm release credentials in release env, not deploy env. +- Put executable deployment behavior in scripts. +- Use metadata to connect Rush projects, package artifacts, and deploy targets. + +Next: [Provider Artifacts](../provider-artifacts). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-mesh.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-mesh.md new file mode 100644 index 0000000..62c28e0 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-mesh.md @@ -0,0 +1,62 @@ +--- +title: "Deploy Mesh" +sidebar_label: "Deploy Mesh" +--- + +The deploy mesh declares deploy targets and their order. It lives at +`.dagger/deploy/services-mesh.yaml`. + +The example mesh is intentionally small: + +```yaml +services: + server: + deploy_after: [] + + webapp: + deploy_after: + - server +``` + +This creates two deploy waves: + +1. `server` +2. `webapp` + +Rush Delivery can run independent targets in the same wave. Dependencies only +express deployment ordering, not application imports. + +## Why The Webapp Waits For The Server + +In the example, the webapp uses production GraphQL URLs that point to the +backend. Deploying the backend first makes the release path easier to reason +about. + +Your project may have different ordering: + +- frontend and backend can deploy in parallel +- a database migration target can run before services +- a documentation site can deploy after generated API docs +- a worker can deploy after a queue or service target + +The mesh should describe operational ordering, not source-code dependency +graphs. Rush already knows source dependencies. + +## Forced Deploys + +The example also has manual workflows that force a single target: + +- `force-deploy-server.yaml` +- `force-deploy-webapp.yaml` + +They call the main workflow with `force_targets_json`. Rush Delivery still uses +the mesh to validate target names and ordering for selected targets. + +## Checklist + +- Add every deployable target to `services`. +- Use `deploy_after` only for real operational dependencies. +- Keep target names aligned with package and deploy metadata filenames. +- Use forced targets for manual redeploys, not duplicate deploy logic. + +Next: [Deploy Targets](../deploy-targets). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-targets.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-targets.md new file mode 100644 index 0000000..f06ad5e --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/deploy-targets.md @@ -0,0 +1,136 @@ +--- +title: "Deploy Targets" +sidebar_label: "Deploy Targets" +--- + +Deploy target metadata tells Rush Delivery how to run a target-specific deploy +script in a Dagger container. The example target files live in +`.dagger/deploy/targets`. + +Each target has two main parts: + +- `deploy_script` +- `runtime` + +## Deploy Script + +The deploy script is product-owned executable behavior. In the example: + +- `server` runs `deploy/cloudrun/scripts/deploy-server.sh` +- `webapp` runs `deploy/cloudflare-pages/scripts/deploy-webapp.sh` + +Rush Delivery prepares the runtime, mounts the package artifact, passes allowed +environment, then executes the script. + +For an OCI package target, Rush Delivery mounts only that target's verified +evidence and passes `ARTIFACT_IMAGE_REFERENCE`, `ARTIFACT_IMAGE_DIGEST`, +`ARTIFACT_IMAGE_REPOSITORY`, `ARTIFACT_IMAGE_PLATFORMS_JSON`, and +`ARTIFACT_SOURCE_REVISION`. The evidence mount is available through +`ARTIFACT_EVIDENCE_DIR`; `ARTIFACT_PATH` is absent. A Cloud Run, Swarm, or +Kubernetes script should send the digest reference directly to its platform and +rely on the platform identity for registry pulls. + +## Runtime Image And Workspace + +The runtime image is the base container for deployment. + +The workspace section limits which repository paths are visible to the deploy +runtime. For the backend, the example includes the deploy bundle, deploy +scripts, smoke tests, and Dockerfile. For the webapp, it includes the static +output and Cloudflare deploy scripts. + +Use a narrow workspace. It keeps deploy containers smaller and avoids +accidentally depending on unrelated files. + +Even `workspace.mode: full` excludes the internal +`.dagger/runtime/evidence` tree. OCI deploy scripts receive only their own +validated evidence at `ARTIFACT_EVIDENCE_DIR` and must not request the internal +tree through workspace metadata. + +## Runtime Install Commands + +Install commands prepare provider tooling. The backend runtime installs Docker +CLI and Google Cloud CLI. The webapp runtime installs Git and uses Wrangler from +the deploy script. + +These commands also participate in toolchain image hashing. If the commands +change, Rush Delivery derives a new toolchain image tag. + +## Environment + +`pass_env` lists environment variables that Rush Delivery may pass from the +deploy env file into the runtime under the same name. `map_env` passes a source +variable under a different target name: + +```yaml +pass_env: + - WEBAPP_URL +map_env: + VITE_GRAPHQL_HTTP: WEBAPP_VITE_GRAPHQL_HTTP +``` + +Static `env` values are set directly by metadata. + +There is no precedence between `pass_env`, `map_env`, and static `env`. All +three add variables to the runtime container. If they produce the same output +name with different values, Rush Delivery fails instead of choosing one +silently. + +Do not declare `ARTIFACT_*`, `GIT_SHA`, or `DRY_RUN`; Rush Delivery owns those +runtime names. + +The backend uses static env to point cloud SDKs at the mounted credentials file: + +```yaml +env: + GOOGLE_APPLICATION_CREDENTIALS: /runtime-files/gcp-credentials.json +``` + +The live value of the credentials file never lives in source. It is copied into +the action runtime files bundle and mounted by metadata. + +## Dry-Run Defaults + +`dry_run_defaults` make dry-runs useful without requiring production secrets. +Every required `pass_env` value and every source variable used by `map_env` that +is not available in dry-run mode should have a harmless placeholder. +For renamed variables, key the default by the source variable name. + +Dry-runs should show what would happen, not accidentally deploy. + +## Runtime Files + +Runtime files are late-bound deploy-platform files. The example maps the Google +auth file in GitHub Actions: + +```yaml +runtime-file-map: | + ${{ steps.auth.outputs.credentials_file_path }}=>gcp-credentials.json +``` + +The backend metadata mounts it with: + +```yaml +file_mounts: + - source: gcp-credentials.json +``` + +Runtime files are not source, cache inputs, package artifacts, or toolchain +image inputs. They are not an OCI credential channel: registry tokens and +Cosign key material remain Package-only environment values. + +## Checklist + +- Keep deploy scripts in the product repository. +- Mount only the workspace paths the script needs. +- Put provider tooling in runtime install commands. +- Use `pass_env` as an allowlist for same-name variables. +- Use `map_env` when the runtime variable name should differ from the source + variable name. +- Use `dry_run_defaults` for harmless dry-run values. +- Use runtime files for deploy-platform credentials and other deploy-only + files, never OCI registry tokens or Cosign key material. +- For OCI targets, deploy `ARTIFACT_IMAGE_REFERENCE` unchanged and do not ask + for Package registry or signing credentials. + +Next: [Validation Targets](../validation-targets). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/github-actions.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/github-actions.md new file mode 100644 index 0000000..385ea1a --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/github-actions.md @@ -0,0 +1,174 @@ +--- +title: "GitHub Actions" +sidebar_label: "GitHub Actions" +--- + +The example repository uses GitHub Actions as a thin Rush Delivery adapter. The +workflows do not calculate deploy plans or run Rush directly. They provide +permissions, credentials, env, runtime files, and action inputs. + +## Pull Request Validation + +The PR workflow uses the `validate` entrypoint: + +```yaml +permissions: + contents: read + packages: read + +steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + toolchain-image-provider: github + rush-cache-provider: github +``` + +No checkout step is needed for normal PR validation. The action passes Git +source coordinates to Dagger, and Rush Delivery acquires the source inside the +Dagger workflow. + +`packages: read` is enough because the validate defaults use `pull-or-build`, +which never publishes provider artifacts. + +If a package target needs build-time env, pass the source values through +`deploy-env` in PR validation too. The metadata allowlist still decides which +values reach the build container. + +If `.dagger/release/npm.yaml` exists, PR validation also verifies Rush change +files before the PR reaches `main`. + +## Main Release Workflow + +The main workflow runs on pushes to `main` and can also be called by manual +force-deploy workflows. + +The job needs stronger permissions: + +```yaml +permissions: + contents: write + id-token: write + packages: write +``` + +The example authenticates to Google Cloud before calling Rush Delivery, then +passes the generated credentials file as a runtime file: + +```yaml +runtime-file-map: | + ${{ steps.auth.outputs.credentials_file_path }}=>gcp-credentials.json +``` + +The deploy env block passes the filesystem targets' product settings and +deploy-platform secrets: + +```yaml +deploy-env: | + GCP_PROJECT_ID=${{ vars.GCP_PROJECT_ID }} + CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }} +``` + +This tutorial's directory/archive targets omit `application-image-provider` and +keep its default `off`; they need no `.dagger/application-images` metadata or +OCI credentials. For an OCI release, switch to the dedicated +[OCI application images tutorial](../oci-application-images), which adds +the package target and provider metadata before selecting the named provider. +Set `docker-socket: ""` in an OCI-only Action job. The +[production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting) cover the +live operational path. + +Rush Delivery reads the deploy env file once, then only passes variables that +package and deploy target metadata allow through `pass_env` or `map_env`. + +## Forced Deploy Workflows + +The example has small manual workflows for single-target deploys. They reuse +the main workflow and pass `force_targets_json`: + +```yaml +with: + force_targets_json: '["server"]' +``` + +This keeps the deployment path identical. Manual deploys still use the same +metadata, provider settings, runtime files, package logic, and deploy mesh. + +## Package Release Workflow + +Package release/versioning can be composed into the main trusted workflow when +the same job should deploy applications and release npm packages: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + dry-run: "false" + release-targets-json: '["npm"]' + deploy-env: | + GCP_PROJECT_ID=${{ vars.GCP_PROJECT_ID }} + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +Rush Delivery shares source acquisition, Rush install cache, and the build +lifecycle, then starts deploy and npm package release side effects after shared +prerequisites pass. Deploy tags still point to the original source SHA. + +For package-only repositories or release debugging, keep a separate standalone +workflow. It uses its own release env and does not touch deploy tags: + +```yaml +permissions: + contents: read + +jobs: + release-packages: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: release-packages + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +Rush Delivery appends `GITHUB_TOKEN` by default, so the release entrypoint can +push the Rush-generated version commit back to the target branch. + +Add `packages` permissions only when provider-backed Rush cache or toolchain +images use GitHub Container Registry. + +## Version Pinning + +Pin Rush Delivery to a released tag: + +```yaml +uses: BootstrapLaboratory/rush-delivery@v0.9.0 +``` + +Advance the tag intentionally when you want new behavior. Do not use an +unversioned branch in production CI. + +## Checklist + +- PR workflow uses `contents: read` and `packages: read`. +- PR workflow uses validate defaults or explicit `pull-or-build` policies. +- Release workflow uses `packages: write`. +- Package release workflow uses `contents: write`. +- Package release workflow uses `release-env` for npm credentials. +- Runtime files carry credential files. +- Deploy env carries settings and secrets. +- Filesystem-only releases omit the application-image provider or keep it + `off`; OCI releases follow the dedicated tutorial and clear the legacy + `docker-socket` input. +- Manual force deploy workflows reuse the main workflow. + +Next: [Local Dry Runs](../local-dry-runs). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/local-dry-runs.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/local-dry-runs.md new file mode 100644 index 0000000..688d19f --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/local-dry-runs.md @@ -0,0 +1,116 @@ +--- +title: "Local Dry Runs" +sidebar_label: "Local Dry Runs" +--- + +CI should usually use Git source mode. Local development often needs a different +path because your latest changes may not be pushed yet. For that, use the +checksummed `rush-delivery-local` launcher described in the +[bounded local-copy guide](../../local-copy-source-imports). + +## Workflow Dry Run + +Run the full workflow without publishing provider artifacts or deploying: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=manual \ + --force-targets-json='[]' \ + --environment=prod \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --application-image-provider=off +``` + +Provider-off local runs are slower than provider-backed CI, but they are simple +and safe. They do not need GHCR permissions. + +## Targeted Dry Run + +To exercise one target, force it: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=manual \ + --force-targets-json='["server"]' \ + --environment=prod \ + --dry-run=true +``` + +Dry-run defaults from deploy target metadata supply harmless values for missing +runtime env. +If the target is an OCI image, provider `off` reports the planned relative image +and platform without requiring or resolving provider credentials or producing a +digest. A supplied aggregate env file is still parsed for other configured +capabilities, so omit live OCI values from dry-run calls. + +## Local PR-Style Validation + +To validate local changes against your main branch: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- validate \ + --event-name=pull_request \ + --pr-base-sha="$(git merge-base HEAD origin/main)" +``` + +This is useful before opening a PR or when debugging validation target metadata. + +## Package Release Dry Run + +To test npm release metadata inside the composed workflow without publishing: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=manual \ + --release-targets-json='["npm"]' \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +To test only the standalone npm release entrypoint: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- release-packages \ + --git-sha="$(git rev-parse HEAD)" \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +This path reads `.dagger/release/npm.yaml` and runs the release build +lifecycle. It does not require `NPM_TOKEN`, does not push a version commit, and +does not publish packages. + +## When To Use Provider-Backed Local Runs + +Provider-backed local runs are possible, but they need the same env values as +CI. Start with provider-off dry-runs unless you are specifically debugging +provider metadata, GHCR access, or cache behavior. + +## Checklist + +- Use the version-matched, checksummed launcher for unpushed changes. +- Keep bounded imports and add only narrow required inclusions. +- Use `--dry-run=true` while developing deploy metadata. +- Use `release-packages --dry-run=true` while developing package release + metadata. +- Use provider-off settings first. +- Use forced targets to shorten feedback loops. + +Next: [Adapt To Your Project](../adapting-to-your-project). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/mixed-node-python-toolchain.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/mixed-node-python-toolchain.md new file mode 100644 index 0000000..6816783 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/mixed-node-python-toolchain.md @@ -0,0 +1,125 @@ +--- +title: "Mixed Node/Python Toolchain" +sidebar_label: "Mixed Node/Python Toolchain" +--- + +This tutorial adds the `uv` Python package manager to Rush Delivery's shared +Node 24 workflow image without a package-level bootstrap script. The result is +available before Rush install and every Rush lifecycle command. + +Read the [toolchain production guide](../../rush-toolchain) first for the trust, +download, extraction, and cache contract. + +## 1. Add Pinned Metadata + +Create `.dagger/toolchains/rush.yaml`: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-toolchain.schema.json +version: rush-delivery-rush-toolchain/v1 +base_image: node:24-bookworm-slim@sha256:65932751ed4073ed02f5c04e494e4b2572a891b7dbea0568a863dc80341bf848 +platform: linux/amd64 +downloads: + - url: https://github.com/astral-sh/uv/releases/download/0.12.2/uv-x86_64-unknown-linux-gnu.tar.gz + sha256: d66e96b5f1ca3b99806eee283a8125d33a0bd669e6e6d9bc4ab7ffda63c41bf4 + format: tar_gz + archive_path: uv-x86_64-unknown-linux-gnu/uv + destination: /usr/local/bin/uv + mode: "0755" +``` + +These values are a complete reviewed tuple. Do not update only the URL, tag, +checksum, member, or base digest. + +## 2. Use The Tool From Rush Scripts + +The project owns its Python dependency policy. A package script can verify and +use the tool before its normal build: + +```json +{ + "scripts": { + "build": "uv --version && uv sync --frozen && node scripts/build.mjs", + "lint": "uv --version && node scripts/lint.mjs", + "test": "uv --version && uv run pytest", + "verify": "uv --version && uv run python -m compileall src" + } +} +``` + +Commit the Python lockfile used by `uv sync --frozen`. Tool acquisition is +deterministic, but project dependency resolution is only deterministic when the +project also locks and verifies its own dependencies. + +## 3. Validate Before Downloading + +```sh +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call validate-metadata-contract --repo=. +``` + +This checks the strict schema/parser and cross-file contract. It does not need +toolchain-provider credentials. + +## 4. Prove Provider-Off Execution + +Run the same validation lifecycle without a toolchain registry: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + validate \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=pull_request \ + --validate-targets-json='["python-worker"]' \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +The first configured run preflights Node 24/Bash/Debian, transfers the pinned +asset, verifies SHA-256, extracts exactly the declared regular member, installs +`/usr/local/bin/uv`, then begins Rush work. A checksum or archive error stops +before installation. + +## 5. Enable Cache In Trusted CI + +After provider-off succeeds, a trusted release job can populate the normal +content-addressed toolchain cache: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + toolchain-image-provider: github + toolchain-image-policy: lazy + rush-cache-provider: github + rush-cache-policy: lazy +``` + +Pull requests should keep read-only behavior: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + toolchain-image-provider: github + toolchain-image-policy: pull-or-build + rush-cache-provider: github + rush-cache-policy: pull-or-build +``` + +The cache tag changes when any ordered toolchain input changes. An +authentication error is not treated as a miss; fix package permissions or +provider credentials rather than rebuilding under an ambiguous identity. + +## 6. Update And Roll Back + +For an upstream update, independently download and hash the new linux/amd64 +asset, inspect the exact archive member, update the complete tuple, and repeat +provider-off acceptance. Populate the new provider cache from a trusted job. + +To roll back, restore the previously reviewed metadata tuple. Removing +`.dagger/toolchains/rush.yaml` returns the project to Rush Delivery's unchanged +Node-only default, but only do that if lifecycle scripts no longer require the +project tool. diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/npm-package-release-baseline.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/npm-package-release-baseline.md new file mode 100644 index 0000000..a3c8339 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/npm-package-release-baseline.md @@ -0,0 +1,104 @@ +--- +title: "NPM Package Release Baseline" +sidebar_label: "NPM Package Release Baseline" +--- + +Rush Delivery package release assumes Rush already owns package identity, +project graph, package commands, and versioning inputs. The framework does not +invent a second package registry model. It calls Rush from an isolated Dagger +runtime and lets Rush decide which packages should publish. + +This chapter uses +[BootstrapLaboratory/labkit](https://github.com/BootstrapLaboratory/labkit) as +the reference shape. LabKit is a package-only Rush monorepo that publishes +public npm packages through Rush Delivery `v0.7.0`. + +## Rush Projects + +Each publishable package is a Rush project in `rush.json`: + +```json +{ + "packageName": "@omgjs/labkit-webapp-ui", + "projectFolder": "packages/webapp-ui", + "reviewCategory": "libraries", + "versionPolicyName": "labkit" +} +``` + +Rush project names and folders are the source of truth for package selection. +Rush Delivery does not maintain a separate list of npm packages. + +## Version Policies + +LabKit uses a Rush version policy in +`common/config/rush/version-policies.json`: + +```json +[ + { + "definitionName": "individualVersion", + "policyName": "labkit" + } +] +``` + +This is normal Rush configuration. Choose the version policy shape that matches +your package lifecycle. Rush Delivery only passes the configured target branch +to Rush change-file verification and Rush publishing. + +## Package Publish Shape + +Each package still owns its npm package metadata: + +```json +{ + "name": "@omgjs/labkit-webapp-ui", + "version": "0.1.1", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "main": "./dist/src/index.js", + "module": "./dist/esm/src/index.js", + "types": "./dist/src/index.d.ts", + "files": ["dist/**/*", "README.md"] +} +``` + +Keep package entrypoints, files, `publishConfig`, README files, and private +package settings in package `package.json` files. That keeps npm behavior +reviewable by package maintainers. + +## Rush Commands + +`release-packages` runs the shared Rush lifecycle before publishing: + +```text +build +lint +test +verify +``` + +Make those commands meaningful for packages. LabKit uses package scripts behind +repo-level Rush commands so the release runtime builds package output before +npm publish starts. + +## Change Files + +With `versioning.strategy: rush-change-files`, PRs should include Rush change +files when package behavior changes. Rush Delivery `validate` runs +`rush change --verify` when `.dagger/release/npm.yaml` exists, so PRs can fail +before they reach `main` if release notes or version bumps are missing. + +## Checklist + +- Every publishable package is a Rush project. +- Rush projects that publish use the intended version policy. +- Package `package.json` files define publishable files and entrypoints. +- Package build scripts produce the files listed in `files`. +- PRs include Rush change files for package changes. +- `build`, `lint`, `test`, and `verify` are ready to run before publish. + +Next: [Release Metadata](../release-metadata). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images.md new file mode 100644 index 0000000..ccd3d6f --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images.md @@ -0,0 +1,121 @@ +--- +title: "Overview" +sidebar_label: "Overview" +description: "Publish, inspect, deploy, hand off, and roll back a signed image." +--- + +This tutorial takes one minimal Rush project from a credential-free image plan +to a signed GHCR publication, local evidence inspection, digest-only deploy, +GitHub Actions, split-stage handoff, and rollback. The checked-in +[canonical example](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo) is the +single source for the project files used throughout the tutorial. + +Rush Delivery `v0.9.0` keeps OCI application images opt-in. A repository whose +selected package artifacts are only `directory` or `rush_deploy_archive` does +not need an application-image provider or OCI credentials. + +## Prerequisites + +For Chapters 1 and 2: + +- a Unix-like workstation with Git, Bash, `tar`, and `jq`; +- Node.js 24 if you run the example directly outside Dagger; +- Dagger CLI `v0.20.7` and a working Dagger engine; +- network access for the initial module, base-image, Rush, and package pulls. + +Chapters 3 and later add Python 3, Cosign `3.1.2`, the GitHub CLI, a GHCR +namespace, and trusted release credentials. Chapter 7 requires Python 3.12 or +newer for safe archive extraction. + +All commands are runnable shell commands unless a block is explicitly labelled +as sanitized output, a manifest example, or a platform-specific excerpt. +Run commands from the tutorial repository root. + +## Create A Clean Tutorial Repository + +Export the tracked example from the immutable release, then give it its own Git +history. This avoids copying generated `common/temp`, `dist`, or `node_modules` +state from another checkout. + +```bash +set -euo pipefail + +TUTORIAL_PARENT="${TMPDIR:-/tmp}/rush-delivery-oci-tutorial" +SOURCE_CHECKOUT="${TMPDIR:-/tmp}/rush-delivery-v0.9.0-source" + +test ! -e "${TUTORIAL_PARENT}" +test ! -e "${SOURCE_CHECKOUT}" +git clone --depth=1 --branch=v0.9.0 \ + https://github.com/BootstrapLaboratory/rush-delivery.git \ + "${SOURCE_CHECKOUT}" +mkdir -p "${TUTORIAL_PARENT}" +git -C "${SOURCE_CHECKOUT}" archive HEAD \ + examples/oci-application-image-rush-repo \ + | tar --extract --directory="${TUTORIAL_PARENT}" --strip-components=2 + +cd "${TUTORIAL_PARENT}" +git init +git config user.name "Rush Delivery tutorial" +git config user.email "rush-delivery-tutorial@example.invalid" +git add --all +git commit -m "chore: initialize OCI image tutorial" + +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export TUTORIAL_REPOSITORY="${TUTORIAL_PARENT}" +export RUSH_DELIVERY_LOCAL="${TMPDIR:-/tmp}/rush-delivery-local-v0.9.0" + +curl --fail --location \ + --output "${RUSH_DELIVERY_LOCAL}" \ + https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.0/rush-delivery-local +printf '%s %s\n' \ + '802ed18dc3bce89974d64884fe3c7ca64f3e206faa4c8c8eef237757101bd391' \ + "${RUSH_DELIVERY_LOCAL}" | sha256sum --check --strict +chmod 0755 "${RUSH_DELIVERY_LOCAL}" +``` + +Sanitized expected output: + +```text +Cloning into '/tmp/rush-delivery-v0.9.0-source'... +Initialized empty Git repository in /tmp/rush-delivery-oci-tutorial/.git/ +[main (root-commit) ] chore: initialize OCI image tutorial +``` + +If `git clone` cannot resolve `v0.9.0`, the release has not been published to +the selected remote. If `git commit` reports no files, confirm that +`examples/oci-application-image-rush-repo` exists in that tag and that GNU or +compatible `tar` honored `--strip-components=2`. + +The example's final image is `scratch` and contains one deterministic payload. +It proves build, evidence, publication, and immutable handoff without hiding a +framework or operating-system runtime in the subject. It is deliberately not a +network service, has no shell, and is not directly usable as a Cloud Run, +Kubernetes, or Swarm application. Replace the Dockerfile with a production +runtime image when adapting the tutorial to a real service. + +## Learning Path + +1. [Build And Scan Target](build-and-scan-target) +2. [Provider-Off Dry Run](provider-off-dry-run) +3. [Registry And Cosign Bootstrap](registry-and-cosign-bootstrap) +4. [Publish And Inspect](publish-and-inspect) +5. [Deploy The Digest](deploy-the-digest) +6. [GitHub Actions](github-actions) +7. [Split Stages And Rollback](split-stages-and-rollback) +8. [Environment-Selected Repository Profiles](environment-profiles) + +## Checkpoint + +```bash +test -f rush.json +test -f .dagger/package/targets/control-plane-api.yaml +test -f .dagger/application-images/providers.yaml +test "$(git status --porcelain)" = "" +dagger version +``` + +The last command should report `dagger v0.20.7`. A different CLI/engine version +is outside the release's validated toolchain until the project explicitly +certifies it. + +Next: [Build And Scan Target](build-and-scan-target). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/build-and-scan-target.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/build-and-scan-target.md new file mode 100644 index 0000000..e520ce3 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/build-and-scan-target.md @@ -0,0 +1,213 @@ +--- +title: "1 - Build And Scan Target" +sidebar_label: "1 - Build And Scan Target" +--- + +This chapter defines what Package builds and what vulnerability policy must pass +before Rush Delivery can publish anything. + +## Prerequisites + +- Complete the [tutorial setup](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs/tutorial/oci-application-images/README.md#create-a-clean-tutorial-repository). +- Work from the root of the exported canonical example. +- Keep the repository clean so generated output is easy to distinguish. + +## Choose The Artifact Boundary + +Choose one artifact kind per deploy target: + +| Kind | Choose it when | Deploy handoff | +| --------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | +| `oci_image` | The service is delivered as a container image and must be scanned, signed, attested, and deployed by digest. | `ARTIFACT_IMAGE_REFERENCE` plus target-scoped evidence | +| `directory` | A platform consumes a built directory, such as static web assets. | `ARTIFACT_PATH` | +| `rush_deploy_archive` | A Node.js service needs Rush's deploy scenario materialized as an archive/directory. | `ARTIFACT_PATH` | + +Only `oci_image` activates the application-image provider contract. Mixing OCI +and filesystem artifacts is supported; the manifest becomes v2 while the +filesystem artifact fields remain unchanged. + +## The Complete Build And Image Inputs + +The example's complete deterministic build script is +[`apps/control-plane-api/scripts/build.mjs`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/apps/control-plane-api/scripts/build.mjs): + +```js +import { + chmod, + mkdir, + readFile, + stat, + utimes, + writeFile, +} from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const projectDirectory = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "..", +); +const sourcePath = path.join(projectDirectory, "src/payload.txt"); +const outputDirectory = path.join(projectDirectory, "dist"); +const outputPath = path.join(outputDirectory, "payload.txt"); +const outputMode = 0o644; +const outputTimestamp = new Date("2000-01-01T00:00:00.000Z"); +const providerEnvironmentNames = [ + "RD_OCI_GHCR_USERNAME", + "RD_OCI_GHCR_TOKEN", + "RD_OCI_COSIGN_PRIVATE_KEY", + "RD_OCI_COSIGN_PASSWORD", + "RD_OCI_COSIGN_PUBLIC_KEY", +]; + +for (const name of providerEnvironmentNames) { + if (Object.hasOwn(process.env, name)) { + throw new Error( + `Tutorial Rush Build received framework-owned provider environment name ${name}.`, + ); + } +} + +const source = await readFile(sourcePath, "utf8"); + +if (process.argv.includes("--check")) { + const output = await readFile(outputPath, "utf8"); + const outputStats = await stat(outputPath); + + if (output !== source) { + throw new Error("Built tutorial payload does not match its source."); + } + if ((outputStats.mode & 0o777) !== outputMode) { + throw new Error("Built tutorial payload does not have mode 0644."); + } + if (outputStats.mtimeMs !== outputTimestamp.getTime()) { + throw new Error( + "Built tutorial payload does not have its fixed timestamp.", + ); + } + + process.stdout.write("Deterministic tutorial payload verified.\n"); +} else { + await mkdir(outputDirectory, { recursive: true }); + await writeFile(outputPath, source, { encoding: "utf8", mode: outputMode }); + await chmod(outputPath, outputMode); + await utimes(outputPath, outputTimestamp, outputTimestamp); + process.stdout.write("Deterministic tutorial payload built.\n"); +} +``` + +The complete +[`apps/control-plane-api/Dockerfile`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/apps/control-plane-api/Dockerfile) +copies only that built output: + +```dockerfile +# checkov:skip=CKV_DOCKER_2:Intentional non-service scratch image has no executable health endpoint +FROM scratch + +COPY --chmod=0444 dist/payload.txt /payload.txt +USER 65532:65532 +``` + +The complete package target is +[`control-plane-api.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml): + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +name: control-plane-api +artifact: + kind: oci_image + context: apps/control-plane-api + dockerfile: apps/control-plane-api/Dockerfile + image: control-plane-api + platform: linux/amd64 + scan: + fail_on: + - high + - critical + ignore_file: .dagger/application-images/grype.yaml +``` + +The complete governed Grype configuration is +[`grype.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/.dagger/application-images/grype.yaml): + +```yaml +# Grype 0.116.1 reads this configuration during OCI packaging. The tutorial +# starts with no vulnerability suppressions. Govern every future exception with +# an owner, reason, review/expiry date, and removal follow-up in adjacent comments. +ignore: [] +``` + +The normal `workflow` and `build-and-package-deploy-targets` paths run the Rush +build before Package. The standalone `package-deploy-targets` function does +not: its `repo` input must already contain the complete built workspace. Do not +point standalone Package at a clean checkout whose Dockerfile copies `dist`. + +`context` and `dockerfile` are repository-relative. The Dockerfile must be +strictly inside its context, so this target resolves it as +`apps/control-plane-api/Dockerfile` inside `apps/control-plane-api`. `image` is +only a lowercase repository suffix; the selected provider later prefixes it +with the registry and repository namespace. `v0.9.0` requires exactly one +explicit normalized platform, here `linux/amd64`. + +Package adds `org.opencontainers.image.revision=` and, +when supplied, `org.opencontainers.image.source=`. The +source URL must not contain credentials, whitespace, or control characters. +The live SHA should be the immutable revision of the exact source being built. + +The `scan.fail_on` list is an exact set, not a severity threshold. A policy of +only `high` rejects High findings but does not implicitly reject Critical +findings. The production example lists both `high` and `critical` intentionally. + +Rush Delivery pins the Grype executable image, but Grype's vulnerability +database/cache changes over time. A previously clean image can therefore fail a +later scan. Treat that as new security information. If an exception is required, +use a supported Grype `ignore` entry and record its vulnerability ID, owner, +reason, review/expiry date, and removal follow-up beside it. Review exceptions +and the database/cache retention policy independently from the pinned scanner +version. + +The supported `v0.9.0` image-build surface is the schema above: one context, +one contained Dockerfile, one image suffix, one platform, trusted source labels, +and the documented scan policy. Do not infer support for metadata-driven build +arguments, secrets, SSH forwarding, extra contexts, multi-platform indexes, or +custom Dockerfile frontends. See the +[package-target schema](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/package-target.schema.json) and +[OCI application-image contract](../../../oci-application-images) for the +bounded surface. + +## Exercise The Deterministic Build + +```bash +set -euo pipefail + +node common/scripts/install-run-rush.js install --max-install-attempts 1 +node common/scripts/install-run-rush.js build --to control-plane-api +cmp \ + apps/control-plane-api/src/payload.txt \ + apps/control-plane-api/dist/payload.txt +``` + +Sanitized expected output: + +```text +Rush Multi-Project Build Tool +... control-plane-api ... SUCCESS ... +``` + +If Rush install fails, resolve package-manager/network trust before proceeding. +If `cmp` fails or `dist/payload.txt` is absent, Package would later fail the +Docker build; repair the normal Rush build rather than generating output in the +Dockerfile. + +## Checkpoint + +```bash +test "$(cat apps/control-plane-api/dist/payload.txt)" = \ + "$(cat apps/control-plane-api/src/payload.txt)" +git check-ignore apps/control-plane-api/dist/payload.txt +``` + +The second command should print `apps/control-plane-api/dist/payload.txt`, +confirming that generated build output is not committed. + +Next: [Provider-Off Dry Run](../provider-off-dry-run). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/deploy-the-digest.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/deploy-the-digest.md new file mode 100644 index 0000000..84ac6da --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/deploy-the-digest.md @@ -0,0 +1,425 @@ +--- +title: "5 - Deploy The Digest" +sidebar_label: "5 - Deploy The Digest" +--- + +This chapter consumes the verified package bundle. Deploy uses the manifest's +immutable digest reference and re-hashes local evidence; it does not rebuild the +image, resolve a tag, query the registry, or rerun Cosign. + +## Prerequisites + +- Complete [Publish And Inspect](../publish-and-inspect). +- Keep `PACKAGE_DIR`, `SOURCE_SHA`, and `RUSH_DELIVERY_MODULE` set. +- The complete package directory, manifest, and evidence must remain together. +- For a named/published OCI artifact, retain the generated + `.dagger/runtime/application-image-credential-capability.json`. Standalone + Deploy reads this frozen, names-only capability first to reject any project + runtime projection of provider credentials; only an older bundle without the + capability falls back to credential names in + `.dagger/application-images/providers.yaml`. It never resolves or uses those + values or performs a registry/Cosign operation. A supplied aggregate Deploy + env file is still parsed for project-owned deployment capabilities. +- The deploy platform—not Rush Delivery's Package credentials—must be able to + import or pull the digest when a real service is used. Cloud Run can consume + a public GHCR reference directly; private GHCR requires a Google Artifact + Registry remote repository with separate upstream authentication. + +## The Complete Generic Deploy Script + +The canonical +[`deploy/consume-image.sh`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/deploy/consume-image.sh) +is provider-neutral and consumes only framework-owned handoff values: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +OCI_EXAMPLE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +: "${OCI_EXAMPLE_DIR}" + +fail() { + printf 'control-plane-api rejected OCI artifact: %s\n' "$1" >&2 + exit 1 +} + +require_artifact_value() { + local name="$1" + [[ -n ${!name-} ]] || fail "${name} is required" +} + +for provider_name in \ + RD_OCI_GHCR_USERNAME \ + RD_OCI_GHCR_TOKEN \ + RD_OCI_COSIGN_PRIVATE_KEY \ + RD_OCI_COSIGN_PASSWORD \ + RD_OCI_COSIGN_PUBLIC_KEY; do + [[ -z ${!provider_name+x} ]] || + fail "framework-owned provider environment name ${provider_name} must be absent" +done + +for name in \ + ARTIFACT_KIND \ + ARTIFACT_IMAGE_NAME \ + ARTIFACT_IMAGE_REFERENCE \ + ARTIFACT_IMAGE_REPOSITORY \ + ARTIFACT_IMAGE_DIGEST \ + ARTIFACT_IMAGE_PLATFORMS_JSON \ + ARTIFACT_SOURCE_REVISION \ + ARTIFACT_EVIDENCE_DIR; do + require_artifact_value "${name}" +done + +[[ ${ARTIFACT_KIND} == oci_image ]] || fail "ARTIFACT_KIND must be oci_image" +[[ ${ARTIFACT_IMAGE_NAME} == control-plane-api ]] || + fail "ARTIFACT_IMAGE_NAME must match this deploy target" +[[ ${ARTIFACT_IMAGE_REPOSITORY} =~ ^[a-z0-9](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs/tutorial/oci-application-images/[a-z0-9.-]*[a-z0-9])?(:[1-9][0-9]{0,4})?/[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*$ ]] || + fail "ARTIFACT_IMAGE_REPOSITORY must be a normalized OCI repository" +[[ ${ARTIFACT_IMAGE_DIGEST} =~ ^sha256:[a-f0-9]{64}$ ]] || + fail "ARTIFACT_IMAGE_DIGEST must be a canonical sha256 digest" +[[ ${ARTIFACT_SOURCE_REVISION} =~ ^[a-f0-9]{40}$ ]] || + fail "ARTIFACT_SOURCE_REVISION must be a full lowercase Git SHA" +[[ ${ARTIFACT_IMAGE_PLATFORMS_JSON} == '["linux/amd64"]' ]] || + fail "ARTIFACT_IMAGE_PLATFORMS_JSON must match the packaged platform" +[[ ${ARTIFACT_IMAGE_REFERENCE} == "${ARTIFACT_IMAGE_REPOSITORY}@${ARTIFACT_IMAGE_DIGEST}" ]] || + fail "ARTIFACT_IMAGE_REFERENCE must equal repository@digest" +[[ -z ${ARTIFACT_PATH+x} ]] || fail "ARTIFACT_PATH must be absent for OCI images" +[[ ${ARTIFACT_EVIDENCE_DIR} == /* ]] || + fail "ARTIFACT_EVIDENCE_DIR must be an absolute path" + +for evidence_file in sbom.spdx.json scan.json provenance.json; do + [[ -f ${ARTIFACT_EVIDENCE_DIR}/${evidence_file} ]] || + fail "ARTIFACT_EVIDENCE_DIR is missing ${evidence_file}" +done + +printf 'control-plane-api accepted immutable image: %s\n' \ + "${ARTIFACT_IMAGE_REFERENCE}" +``` + +Rush Delivery executes it inside the deploy target's declared runtime. The +script validates the generic contract but does not start or pull the tutorial's +`scratch` image. That image has no shell, network server, or operating-system +runtime; it is a supply-chain/handoff subject, not a deployable web service. + +## Run Planned And Published Deploys + +First, a provider-off planned manifest is valid only for a Deploy dry run: + +```bash +dagger -m "${RUSH_DELIVERY_MODULE}" call deploy-release \ + --repo="${PLAN_DIR}" \ + --git-sha="${TUTORIAL_DRY_SHA}" \ + --release-targets-json='["control-plane-api"]' \ + --environment=prod \ + --dry-run=true \ + --toolchain-image-provider=off \ + --package-manifest-file="${PLAN_DIR}/.dagger/runtime/package-manifest.json" +``` + +Then execute the complete generic script against the published bundle: + +```bash +dagger -m "${RUSH_DELIVERY_MODULE}" call deploy-release \ + --repo="${PACKAGE_DIR}" \ + --git-sha="${SOURCE_SHA}" \ + --release-targets-json='["control-plane-api"]' \ + --environment=prod \ + --dry-run=false \ + --toolchain-image-provider=off \ + --package-manifest-file="${PACKAGE_DIR}/.dagger/runtime/package-manifest.json" +``` + +Raw/standalone `deploy-release` intentionally performs deploy only and does not +move `deploy/prod/...` tags because that entrypoint has no configured tag-update +capability. The composed Git-source `workflow` supplies its explicit source-auth +token capability and updates deploy tags. In a split pipeline, use the composed +workflow when tag movement is required, or make tag movement a separate, +protected control-plane action. + +Sanitized expected live output: + +```text +control-plane-api accepted immutable image: ghcr.io//rush-delivery-tutorial/control-plane-api@sha256:<64-lowercase-hex> +``` + +## Publication Identity Versus Pull Identity + +The Package username/token writes the subject plus its digest-derived Cosign +signature/attestation attachments. Those credentials never reach Deploy. +`ARTIFACT_IMAGE_REFERENCE` identifies the published subject +but grants no access to it. Kubernetes image-pull credentials, Swarm node +credentials, or the equivalent platform control plane must independently +receive least-privilege access to the private repository. Cloud Run is a +special case: it imports public GHCR images directly, but private GHCR images +must be exposed through an authenticated Artifact Registry remote repository. +The Cloud Run service identity selected with `--service-account` is the +application's runtime identity; it is not the upstream image-import identity. +Public GHCR packages do not need private pull credentials, but their visibility +is a separate registry policy decision. The SPDX and provenance attestations +are stored with the image and can disclose dependency inventory, source URI, +and build parameters; classify those predicates before making the package +public. + +## Framework Runtime Variables + +Rush Delivery reserves the entire `ARTIFACT_*` namespace, including future +names, plus `GIT_SHA` and `DRY_RUN`. Deploy `runtime.env`, `pass_env`, `map_env`, +`dry_run_defaults`, `required_host_env`, and host-path source variables must not +write or repurpose them—even with the same value. + +| Variable | Planned OCI dry run | Published OCI live/dry run | +| ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------ | +| `ARTIFACT_KIND` | `oci_image` | `oci_image` | +| `ARTIFACT_IMAGE_NAME` | package `image` suffix | package `image` suffix | +| `ARTIFACT_IMAGE_PLATFORMS_JSON` | one-platform JSON array | one-platform JSON array | +| `ARTIFACT_SOURCE_REVISION` | full manifest SHA | full manifest SHA | +| `ARTIFACT_IMAGE_REPOSITORY` | absent with provider `off`; present with named provider | normalized registry repository | +| `ARTIFACT_IMAGE_DIGEST` | absent | lowercase `sha256:...` | +| `ARTIFACT_IMAGE_REFERENCE` | absent | exact `repository@sha256:...` | +| `ARTIFACT_EVIDENCE_DIR` | absent | `/workspace/.dagger/runtime/evidence/` | +| `ARTIFACT_PATH` | always absent for OCI | always absent for OCI | +| `GIT_SHA` | invocation SHA | invocation SHA, preflight-matched to `source_revision` | +| `DRY_RUN` | `1` | `0` live, `1` dry run | + +The generic workspace excludes all framework evidence. After global integrity +preflight, Rush Delivery mounts only the current published target's evidence at +`ARTIFACT_EVIDENCE_DIR`; sibling targets and filesystem targets do not receive +it. + +## Complete Deploy Result Examples + +These are complete sanitized objects that satisfy the public deploy-result +contract. `output` contains the deploy script/dry-run summary, not a hidden +filesystem artifact path. + +Planned dry run: + +```json +{ + "dryRun": true, + "environment": "prod", + "plan": { + "selectedTargets": ["control-plane-api"], + "waves": [[{ "target": "control-plane-api" }]] + }, + "results": [ + { + "artifactImage": "control-plane-api", + "artifactKind": "oci_image", + "output": "[deploy-release] dry-run target=control-plane-api wave=1\nenvironment=prod\ngitSha=0123456789abcdef0123456789abcdef01234567\ndeploy_tag=deploy/prod/control-plane-api\ndeploy_script=deploy/consume-image.sh\npackage_artifact_kind=oci_image\npackage_artifact_status=planned\npackage_artifact_image=control-plane-api\npackage_artifact_platforms=[\"linux/amd64\"]\npackage_artifact_publication=no-image-or-digest-produced-dry-run\nimage=node:24-bookworm-slim\nenv:\n - ARTIFACT_IMAGE_NAME=control-plane-api\n - ARTIFACT_IMAGE_PLATFORMS_JSON=[\"linux/amd64\"]\n - ARTIFACT_KIND=oci_image\n - ARTIFACT_SOURCE_REVISION=0123456789abcdef0123456789abcdef01234567\n - DRY_RUN=1\n - GIT_SHA=0123456789abcdef0123456789abcdef01234567\nworkspace:\n mode=partial\n file=deploy/consume-image.sh\n", + "status": "success", + "target": "control-plane-api", + "wave": 1 + } + ] +} +``` + +Published live run: + +```json +{ + "dryRun": false, + "environment": "prod", + "plan": { + "selectedTargets": ["control-plane-api"], + "waves": [[{ "target": "control-plane-api" }]] + }, + "results": [ + { + "artifactImage": "control-plane-api", + "artifactKind": "oci_image", + "artifactReference": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "output": "control-plane-api accepted immutable image: ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", + "status": "success", + "target": "control-plane-api", + "wave": 1 + } + ] +} +``` + +`artifactReference` is optional because planned dry-run results do not invent a +published identity. `artifactImage` and `artifactKind` are always present for +OCI results. `artifactPath` belongs only to filesystem results and is never +fabricated for OCI. + +## Platform-Specific Excerpts + +The next commands are excerpts, not runnable against the tutorial's non-service +`scratch` subject. Adapt service names, runtime configuration, and pull identity +only after replacing the Dockerfile with a real application. Kubernetes and +Swarm pass the verified reference unchanged. Cloud Run can do the same for a +public GHCR package; a private GHCR package uses a deterministic Artifact +Registry remote-repository coordinate while preserving the verified digest. + +Cloud Run excerpt for a **public** GHCR package: + +```bash +gcloud run deploy "${CLOUD_RUN_SERVICE}" \ + --image="${ARTIFACT_IMAGE_REFERENCE}" \ + --region="${CLOUD_RUN_REGION}" \ + --service-account="${CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT}" +``` + +For a **private** GHCR package, first provision a Docker-format Artifact +Registry remote repository whose immutable upstream is `https://ghcr.io`, +configure its upstream username/token through Secret Manager, and grant the +Cloud Run deployment control plane access to that repository. Then map only the +registry coordinate and retain the manifest digest: + +```bash +: "${CLOUD_RUN_GHCR_REMOTE_PREFIX:?expected LOCATION-docker.pkg.dev/PROJECT/REMOTE_REPOSITORY}" + +case "${ARTIFACT_IMAGE_REPOSITORY}" in + ghcr.io/*) upstream_image="${ARTIFACT_IMAGE_REPOSITORY#ghcr.io/}" ;; + *) printf 'expected a ghcr.io artifact repository\n' >&2; exit 1 ;; +esac + +CLOUD_RUN_IMAGE_REFERENCE="${CLOUD_RUN_GHCR_REMOTE_PREFIX}/${upstream_image}@${ARTIFACT_IMAGE_DIGEST}" +gcloud run deploy "${CLOUD_RUN_SERVICE}" \ + --image="${CLOUD_RUN_IMAGE_REFERENCE}" \ + --region="${CLOUD_RUN_REGION}" \ + --service-account="${CLOUD_RUN_RUNTIME_SERVICE_ACCOUNT}" +``` + +Google documents both the +[Cloud Run registry restrictions and private-GHCR remote-repository path](https://docs.cloud.google.com/run/docs/deploying) +and the exact +[Artifact Registry GHCR digest coordinate](https://docs.cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling). +Provision and test that mapping before production; do not replace the digest +with a tag. The `--service-account` value controls the running application's +Google API identity, as described by +[Cloud Run service identity](https://docs.cloud.google.com/run/docs/securing/service-identity), +not Artifact Registry's authentication to GHCR. + +Kubernetes excerpt: + +```bash +kubectl --namespace="${KUBE_NAMESPACE}" set image \ + deployment/"${KUBE_DEPLOYMENT}" \ + app="${ARTIFACT_IMAGE_REFERENCE}" +kubectl --namespace="${KUBE_NAMESPACE}" rollout status \ + deployment/"${KUBE_DEPLOYMENT}" +``` + +Docker Swarm excerpt: + +```bash +set -euo pipefail + +: "${SWARM_PULL_USERNAME:?dedicated pull-only registry username is required}" +: "${SWARM_PULL_TOKEN:?dedicated pull-only registry token is required}" +SWARM_REGISTRY="${ARTIFACT_IMAGE_REPOSITORY%%/*}" +SWARM_DOCKER_CONFIG="$(mktemp -d "${TMPDIR:-/tmp}/rush-delivery-swarm-auth.XXXXXX")" +chmod 0700 "${SWARM_DOCKER_CONFIG}" +trap 'find "${SWARM_DOCKER_CONFIG}" -depth -delete' EXIT + +printf '%s' "${SWARM_PULL_TOKEN}" | \ + DOCKER_CONFIG="${SWARM_DOCKER_CONFIG}" docker login \ + --username "${SWARM_PULL_USERNAME}" \ + --password-stdin \ + "${SWARM_REGISTRY}" +unset SWARM_PULL_TOKEN + +DOCKER_CONFIG="${SWARM_DOCKER_CONFIG}" docker service update \ + --image="${ARTIFACT_IMAGE_REFERENCE}" \ + --with-registry-auth \ + "${SWARM_SERVICE}" +``` + +The Docker CLI in the Swarm excerpt is a deploy-platform requirement, not an +OCI Package requirement. It may require a socket in that specific deploy +runtime; Cloud Run/Kubernetes integrations do not inherit that requirement. +Docker documents that `--with-registry-auth` sends registry authentication to +Swarm agents, so the isolated login must use a distinct pull-only identity, +never the Package publisher token. The temporary Docker configuration is +removed on exit. See Docker's +[`service update` reference](https://docs.docker.com/reference/cli/docker/service/update/) +and +[Swarm service authentication guidance](https://docs.docker.com/engine/swarm/services/). + +## Safe Failure Exercises + +Run these only against disposable copies below `${TMPDIR:-/tmp}`. They use no +provider credentials and fail before a live deploy script starts. + +```bash +set -euo pipefail + +FAILURE_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/rush-delivery-oci-failures.XXXXXX")" +cp -a "${PACKAGE_DIR}" "${FAILURE_ROOT}/source-mismatch" +cp -a "${PACKAGE_DIR}" "${FAILURE_ROOT}/mutable-reference" +cp -a "${PACKAGE_DIR}" "${FAILURE_ROOT}/missing-evidence" +cp -a "${PACKAGE_DIR}" "${FAILURE_ROOT}/modified-evidence" + +expect_deploy_failure() { + local repo="$1" + local sha="$2" + if dagger -m "${RUSH_DELIVERY_MODULE}" call deploy-release \ + --repo="${repo}" \ + --git-sha="${sha}" \ + --release-targets-json='["control-plane-api"]' \ + --environment=prod \ + --dry-run=false \ + --toolchain-image-provider=off \ + --package-manifest-file="${repo}/.dagger/runtime/package-manifest.json" + then + printf 'expected deploy failure for %s\n' "${repo}" >&2 + return 1 + fi +} + +# Source mismatch. +expect_deploy_failure \ + "${FAILURE_ROOT}/source-mismatch" \ + ffffffffffffffffffffffffffffffffffffffff + +# Planned artifact used for a live deploy. +expect_deploy_failure "${PLAN_DIR}" "${TUTORIAL_DRY_SHA}" + +# Mutable reference rejected by strict manifest parsing. +MUTABLE_MANIFEST="${FAILURE_ROOT}/mutable-reference/.dagger/runtime/package-manifest.json" +jq '.artifacts["control-plane-api"].reference = + "ghcr.io/acme/rush-delivery-tutorial/control-plane-api:latest"' \ + "${MUTABLE_MANIFEST}" > "${MUTABLE_MANIFEST}.new" +mv "${MUTABLE_MANIFEST}.new" "${MUTABLE_MANIFEST}" +expect_deploy_failure "${FAILURE_ROOT}/mutable-reference" "${SOURCE_SHA}" + +# Missing local evidence. +mv \ + "${FAILURE_ROOT}/missing-evidence/.dagger/runtime/evidence/control-plane-api/scan.json" \ + "${FAILURE_ROOT}/missing-evidence/scan.json.missing" +expect_deploy_failure "${FAILURE_ROOT}/missing-evidence" "${SOURCE_SHA}" + +# Evidence whose bytes no longer match the manifest digest. +printf '\n' >> \ + "${FAILURE_ROOT}/modified-evidence/.dagger/runtime/evidence/control-plane-api/scan.json" +expect_deploy_failure "${FAILURE_ROOT}/modified-evidence" "${SOURCE_SHA}" +``` + +Expected diagnostic meanings: + +- source mismatch: invocation `gitSha` does not equal the artifact's + `source_revision`; +- planned-live: live Deploy requires `status: published`; +- mutable reference: published references must be lowercase digest references; +- missing evidence: the target-owned evidence path is unreadable; +- evidence hash: bytes changed after Package and no longer match the manifest. + +Do not “repair” these failures by editing the unsigned manifest. Restore the +trusted package bundle and independently recorded SHA/checksum instead. + +## Checkpoint + +```bash +REFERENCE="$(jq -r '.artifacts["control-plane-api"].reference' \ + "${PACKAGE_DIR}/.dagger/runtime/package-manifest.json")" +[[ ${REFERENCE} == *@sha256:* ]] +[[ ${REFERENCE} != *:latest ]] +``` + +The successful live `deploy-release` result must contain exactly `REFERENCE` as +`artifactReference`. + +Next: [GitHub Actions](../github-actions). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/environment-profiles.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/environment-profiles.md new file mode 100644 index 0000000..2d02966 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/environment-profiles.md @@ -0,0 +1,188 @@ +--- +title: "8 - Environment Profiles" +sidebar_label: "8 - Environment Profiles" +--- + +This tutorial keeps one application-image provider definition unchanged while +staging and production select different public registry coordinates. Package +resolves the chosen coordinates once; Deploy consumes only the packaged digest +and never reconstructs a repository from the current environment. + +Complete the [provider-off](../provider-off-dry-run) and +[publication](../publish-and-inspect) chapters first. + +## 1. Define One Provider + +Use exactly one registry field and one repository-prefix field. This example +selects both from the environment while keeping credential roles separate. The +repository keeps the same provider and coordinate-only env files under the +[deployment compatibility examples](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/deployment-environment-compatibility): + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + release: + kind: oci_registry + registry_env: APP_IMAGE_REGISTRY + repository_prefix_env: APP_IMAGE_REPOSITORY_PREFIX + username_env: APP_IMAGE_USERNAME + token_env: APP_IMAGE_TOKEN + signing_key_env: APP_IMAGE_SIGNING_KEY + signing_password_env: APP_IMAGE_SIGNING_PASSWORD + verification_key_env: APP_IMAGE_VERIFICATION_KEY +``` + +`APP_IMAGE_REGISTRY` and `APP_IMAGE_REPOSITORY_PREFIX` are public routing +values. The five credential values remain protected Package capabilities. A +coordinate name cannot alias either coordinate role, any application/Rush +cache/toolchain/npm/source credential name, `GIT_SHA`, `DRY_RUN`, or the +framework-owned `ARTIFACT_` namespace. + +Static/environment mixed definitions are also valid. For example, keep +`registry: ghcr.io` while using only `repository_prefix_env`. + +## 2. Create Deployment Profiles + +Use separate protected CI environment files. The sample coordinate values are +public; the placeholders are not usable credentials: + +```dotenv title="staging.env" +APP_IMAGE_REGISTRY=ghcr.io +APP_IMAGE_REPOSITORY_PREFIX=example-inc/staging +APP_IMAGE_USERNAME=ci-staging +APP_IMAGE_TOKEN=replace-in-secret-store +APP_IMAGE_SIGNING_KEY=replace-in-secret-store +APP_IMAGE_SIGNING_PASSWORD=replace-in-secret-store +APP_IMAGE_VERIFICATION_KEY=replace-in-secret-store +``` + +```dotenv title="production.env" +APP_IMAGE_REGISTRY=ghcr.io +APP_IMAGE_REPOSITORY_PREFIX=example-inc/production +APP_IMAGE_USERNAME=ci-production +APP_IMAGE_TOKEN=replace-in-secret-store +APP_IMAGE_SIGNING_KEY=replace-in-secret-store +APP_IMAGE_SIGNING_PASSWORD=replace-in-secret-store +APP_IMAGE_VERIFICATION_KEY=replace-in-secret-store +``` + +Never commit real tokens, private keys, or passwords. Multiline key values in +Action env content use literal `\n` separators as described in the +[production guide](../../../oci-application-images). + +Registry values are authorities such as `ghcr.io` or `registry.example:5443`, +without scheme, userinfo, or path. Repository prefixes are normalized lowercase +OCI paths without tag, digest, whitespace, or traversal. Invalid dynamic values +produce an error naming the provider, role, and environment variable—not the +raw value. + +## 3. Run Credential-Free Named Dry Runs + +A named dry run resolves the selected public coordinates but reads none of the +five provider credential values. Make a coordinate-only file for planning: + +```dotenv title="staging-plan.env" +APP_IMAGE_REGISTRY=ghcr.io +APP_IMAGE_REPOSITORY_PREFIX=example-inc/staging +``` + +```sh +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --deploy-env-file=staging-plan.env \ + --git-sha="$(git rev-parse HEAD)" \ + --source-repository-url=https://github.com/example-inc/platform.git \ + --application-image-provider=release \ + --dry-run=true \ + --export=staging-plan +``` + +Inspect `.dagger/runtime/package-manifest.json`. The planned OCI artifact must +name `ghcr.io/example-inc/staging/`, have status `planned`, and have +no published digest/reference. + +Repeat with production coordinate values and confirm only the planned +repository changes. The source revision and package target remain unchanged. + +Provider `off` dry runs do not read provider metadata or coordinates and emit +relative image intent. A selection with no OCI target reads no provider data at +all, even when a global provider input is present. + +## 4. Understand Environment Ownership + +The composed `workflow` merges `workflow-env` with the deploy overlay before +Package. A key present in both files is accepted only when its value is equal; +different duplicates fail instead of treating deploy values as overrides. + +Standalone `package-deploy-targets` and +`build-and-package-deploy-targets` resolve coordinates only from +`deploy-env-file`. Release env, project Build projections, resolved Deploy +runtime env, and runtime-file bundles are not coordinate sources. + +Coordinate values are never projected automatically into project Build or +Deploy code. Add a separate target-owned mapping only if project code has an +independent need for a public value. + +## 5. Publish Staging + +Load staging coordinates and credentials through the selected CI environment, +then run live Package: + +```sh +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --deploy-env-file=staging.env \ + --git-sha="$(git rev-parse HEAD)" \ + --source-repository-url=https://github.com/example-inc/platform.git \ + --application-image-provider=release \ + --dry-run=false \ + --export=staging-package +``` + +Verify the manifest, signature, provenance attestation, SBOM attestation, scan +evidence, and canonical digest exactly as in +[publish and inspect](../publish-and-inspect). Every record must use the same +resolved staging repository. + +## 6. Deploy The Packaged Digest + +Pass the packaged workspace and its manifest to Deploy. Do not supply a new +provider or ask Deploy to read the production profile: + +```sh +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call deploy-release \ + --repo=staging-package \ + --git-sha="$(git rev-parse HEAD)" \ + --release-targets-json='["control-plane-api"]' \ + --environment=staging \ + --dry-run=false \ + --package-manifest-file=staging-package/.dagger/runtime/package-manifest.json +``` + +Deploy receives `ARTIFACT_IMAGE_REFERENCE` as the verified +`repository@sha256:...`. Promotion to production means running Package with the +production profile and verifying that new publication; it is not a mutable tag +rewrite during Deploy. + +## 7. Production Gate + +Before promotion, require: + +- both named dry runs select the intended normalized repositories; +- dry runs succeed with credential values absent; +- environment protection separates staging and production credentials; +- the live subject and every signature/attestation/evidence record agree on one + canonical repository and digest; +- Deploy consumes the packaged manifest and never reloads provider metadata; +- partial-publication cleanup is scoped to the resolved repository; and +- a rollback retains the original packaged manifest/evidence and deploys the + verified digest, not a reconstructed tag. + +For failures, use the +[application-image troubleshooting guide](../../../oci-application-image-troubleshooting) +and [registry recipes](../../../oci-registry-recipes). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/github-actions.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/github-actions.md new file mode 100644 index 0000000..801b782 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/github-actions.md @@ -0,0 +1,278 @@ +--- +title: "6 - GitHub Actions" +sidebar_label: "6 - GitHub Actions" +--- + +This chapter keeps the existing filesystem-compatible Action path unchanged, +then adds OCI publication as a separate trusted opt-in. The composite Action +supports composed `workflow`, `validate`, and `release-packages`; stage-level +Package functions remain raw Dagger module calls. + +## Action Reference Policy + +GitHub treats only a full 40-character commit SHA as an immutable action +reference. The third-party actions below are pinned to reviewed full SHAs and +retain a release-version comment for dependency updates. Enable Dependabot (or +an equivalent reviewed updater) for those pins. Rush Delivery references remain +`@v0.9.0` here so every example states the release contract being taught; a +strict production repository should resolve that reviewed release tag, +verify it against the release record, and replace the tag with its full commit +SHA before merging the workflow. This is required when the repository or +organization enables GitHub's full-SHA action policy. See GitHub's +[secure action reference guidance](https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions) +and +[full-SHA enforcement setting](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#allowing-select-actions-and-reusable-workflows-to-run). + +## Prerequisites + +- Complete [Deploy The Digest](../deploy-the-digest). +- Commit and push the package/provider/deploy metadata to the target repository. +- Create a protected `production` GitHub environment with required reviewers or + equivalent deployment policy. +- Store the five values from Chapter 3 in that environment. +- The repository's GHCR package policy must allow the dedicated PAT identity + created in Chapter 3 to write the subject and its digest-derived `.sig`/`.att` + attachment tags. + +## Filesystem-Compatible Baseline + +Start with a workflow that has no OCI credentials and leaves the application +provider off. This shape remains valid for existing `directory` and +`rush_deploy_archive` repositories; in the tutorial repository it produces a +credential-free OCI plan because the selected target is `oci_image`. + +```yaml +name: release-plan + +on: + pull_request: + +permissions: + contents: read + +jobs: + plan: + runs-on: ubuntu-latest + steps: + - name: Plan Rush Delivery + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + git-sha: ${{ github.event.pull_request.head.sha }} + event-name: workflow_call + force-targets-json: '["control-plane-api"]' + dry-run: "true" + toolchain-image-provider: off + rush-cache-provider: off + application-image-provider: off +``` + +Sanitized expected output includes `status=planned` and contains no repository, +digest, evidence, registry request, or OCI credential lookup. Fork pull requests +receive no live OCI credential because none is referenced by the job. + +The Action's `docker-socket` input retains a non-empty default for compatibility +with existing project-owned deploy scripts that invoke Docker. That default is +not an OCI image-build dependency. Leave existing filesystem jobs unchanged +unless their own deploy scripts also do not need the socket. Treat a mounted +host socket as host-level authority: trusted project code can ask the daemon to +mount runner paths and bypass Dagger workspace or secret-file isolation. Never +give that socket to untrusted checkout code. + +## Trusted OCI Release Job + +Add live OCI as a separate job only after the package target and literal GHCR +provider metadata are reviewed. Replace `acme/control-plane` in the repository +guard with the actual owner/repository. + +```yaml +name: publish-and-deploy-image + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish-and-deploy: + if: >- + github.repository == 'acme/control-plane' && + github.ref == 'refs/heads/main' && + (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + environment: production + permissions: + contents: write + steps: + - name: Publish and deploy verified OCI image + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + git-sha: ${{ github.sha }} + event-name: workflow_call + force-targets-json: '["control-plane-api"]' + environment: prod + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + application-image-provider: ghcr + docker-socket: "" + source-mode: git + source-repository-url: https://github.com/${{ github.repository }}.git + source-ref: ${{ github.sha }} + source-auth-token-env: GITHUB_TOKEN + deploy-env: | + RD_OCI_GHCR_USERNAME=${{ vars.RD_OCI_GHCR_USERNAME }} + RD_OCI_GHCR_TOKEN=${{ secrets.RD_OCI_GHCR_TOKEN }} + RD_OCI_COSIGN_PRIVATE_KEY=${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD=${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY=${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} +``` + +The PEM secrets above must already contain literal `\n` pairs and no physical +newline. `contents: write` lets the composed Git-source workflow move its deploy +tag after a successful deploy. This tutorial authenticates GHCR with the +dedicated PAT in `RD_OCI_GHCR_TOKEN`; the PAT's `write:packages` scope and its +account/package access permit subject and Cosign attachment-tag writes. A workflow-level +`packages: write` permission would affect only `${{ github.token }}` and would +not narrow or strengthen this PAT, so it is intentionally absent. If you replace +the PAT mapping with `${{ github.actor }}` plus `${{ github.token }}`, add +`packages: write` to this trusted job and re-check the package's Actions access. +Key-backed Cosign mode does not need `id-token` or `attestations` permission. + +`docker-socket: ""` is required in this OCI-only job to prove that Dagger-native +image build/publication and the generic deploy script do not depend on the host +socket. Add a socket only to a different job whose project-owned deploy script +actually invokes Docker and whose checkout is fully trusted. + +Environment approval and the repository/ref/event guard keep live credentials +out of untrusted pull requests and forks. Do not weaken the condition to run on +`pull_request_target` with untrusted checkout content. A fork PR should run the +provider-off baseline; it should never receive GHCR write credentials or the +Cosign private key. + +Sanitized expected output proceeds through provider preflight, all preparation, +ordered publication/verification, the generic deploy script, and a +`deploy/prod/control-plane-api` tag update. A skipped job means the event/ref or +repository guard was not trusted; that is expected on PRs and forks. + +## Stage-Level Package Publication Uses Raw Dagger + +The composite Action does not expose `package-deploy-targets` or +`build-and-package-deploy-targets` as `entrypoint` values. A split-stage job must +install the pinned Dagger CLI, then invoke the `v0.9.0` module directly. This +complete job publishes and exports the package directory without running +Deploy: + +```yaml +name: package-image-bundle + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + package: + if: >- + github.repository == 'acme/control-plane' && + github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + environment: production + permissions: + contents: read + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + with: + fetch-depth: 0 + + - name: Install Dagger CLI + uses: dagger/dagger-for-github@27b130bf0f79a7f6fbbbe0fbca6760dc9bb40a77 # v8.4.1 + with: + version: v0.20.7 + + - name: Build, publish, and export package bundle + shell: bash + env: + RD_OCI_GHCR_USERNAME: ${{ vars.RD_OCI_GHCR_USERNAME }} + RD_OCI_GHCR_TOKEN: ${{ secrets.RD_OCI_GHCR_TOKEN }} + RD_OCI_COSIGN_PRIVATE_KEY: ${{ secrets.RD_OCI_COSIGN_PRIVATE_KEY }} + RD_OCI_COSIGN_PASSWORD: ${{ secrets.RD_OCI_COSIGN_PASSWORD }} + RD_OCI_COSIGN_PUBLIC_KEY: ${{ secrets.RD_OCI_COSIGN_PUBLIC_KEY }} + run: | + set -euo pipefail + umask 077 + + DEPLOY_ENV_FILE="${RUNNER_TEMP}/rush-delivery-oci.env" + trap 'rm -f -- "${DEPLOY_ENV_FILE}"' EXIT + { + printf 'RD_OCI_GHCR_USERNAME=%s\n' "${RD_OCI_GHCR_USERNAME}" + printf 'RD_OCI_GHCR_TOKEN=%s\n' "${RD_OCI_GHCR_TOKEN}" + printf 'RD_OCI_COSIGN_PRIVATE_KEY=%s\n' "${RD_OCI_COSIGN_PRIVATE_KEY}" + printf 'RD_OCI_COSIGN_PASSWORD=%s\n' "${RD_OCI_COSIGN_PASSWORD}" + printf 'RD_OCI_COSIGN_PUBLIC_KEY=%s\n' "${RD_OCI_COSIGN_PUBLIC_KEY}" + } > "${DEPLOY_ENV_FILE}" + + dagger \ + -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + call build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --artifact-prefix=deploy-target \ + --deploy-env-file="${DEPLOY_ENV_FILE}" \ + --dry-run=false \ + --git-sha="${GITHUB_SHA}" \ + --source-repository-url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" \ + --application-image-provider=ghcr \ + export --path="${RUNNER_TEMP}/oci-package" + + test -f "${RUNNER_TEMP}/oci-package/.dagger/runtime/package-manifest.json" +``` + +The first Dagger Action step installs only the pinned CLI because no `args`, +`call`, or `shell` input is supplied. The following Bash step is the explicit +raw module call. Do not rewrite it as a Rush Delivery composite `entrypoint`; +that would imply a public surface the Action does not provide. + +## Failure Meaning + +- Missing environment values: the protected environment or secret/variable + mapping is incomplete; do not substitute PR-visible values. +- `applicationImageProvider` unknown: the committed provider name does not match + `ghcr` exactly. +- Docker socket errors in the OCI-only job: the input was not set to the empty + string or a project deploy script still depends on Docker. +- `Resource not accessible by integration`: `${{ github.token }}` lacks the job + permission needed for source or deploy-tag access. A GHCR denial in this + tutorial instead means the PAT identity, scope, SSO authorization, or package + policy is wrong. +- A post-publish Cosign failure can leave a subject, navigation tag, `.sig`, + `.att`, or untagged historical package version; inspect the reported canonical + reference and full package inventory before retrying. +- Composite `entrypoint` rejected: only `workflow`, `validate`, and + `release-packages` are supported; use the raw module call for stage functions. + +## Checkpoint + +In the trusted job, inspect the result and require all of the following before +promotion: + +```text +provider/key preflight succeeded +manifest status is published +reference contains @sha256: +generic deploy consumed the same reference +OCI-only job had docker-socket set to the empty string +no live OCI job ran for a pull request or fork +``` + +The GHCR package should contain exactly one subject plus at least two non-subject +package versions for the signature and combined attestation attachment. It may +contain additional untagged history. Registry UI counts remain secondary: the +manifest plus successful signature, SPDX-attestation, and provenance-attestation +verification are the release contract. + +Next: [Split Stages And Rollback](../split-stages-and-rollback). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/provider-off-dry-run.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/provider-off-dry-run.md new file mode 100644 index 0000000..68ec0f7 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/provider-off-dry-run.md @@ -0,0 +1,159 @@ +--- +title: "2 - Provider-Off Dry Run" +sidebar_label: "2 - Provider-Off Dry Run" +--- + +This chapter plans the OCI target without registry or signing credentials and +without publishing an image. + +## Prerequisites + +- Complete [Build And Scan Target](../build-and-scan-target). +- Dagger CLI/engine `v0.20.7` must be running. +- Do not create an OCI env file or export any `RD_OCI_*` credentials yet. + +Use the exact synthetic full SHA below for this dry-run exercise. It is valid +shape-wise but is deliberately not a production source identity. + +```bash +set -euo pipefail + +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export TUTORIAL_DRY_SHA="0123456789abcdef0123456789abcdef01234567" +test "${#TUTORIAL_DRY_SHA}" -eq 40 +``` + +## Validate Metadata First + +Repository-wide validation checks package, provider, deploy, and cross-file +contracts without running the release workflow: + +```bash +dagger -m "${RUSH_DELIVERY_MODULE}" call validate-metadata-contract \ + --repo=. +``` + +Sanitized expected output: + +```json +{ + "deploy_targets": ["control-plane-api"], + "package_targets": ["control-plane-api"], + "release_targets": [], + "rush_projects": ["control-plane-api"], + "validation_targets": [] +} +``` + +The important checkpoint is a successful command with exactly the expected +target/project names. A failure here means the `.dagger` files disagree; fix it +before interpreting a workflow failure as a registry or build problem. + +## Run The Composed Dry Run + +Provider `off` is the explicit credential-free planning mode: + +```bash +"${RUSH_DELIVERY_LOCAL}" \ + --module="${RUSH_DELIVERY_MODULE}" \ + --repo=. -- workflow \ + --git-sha="${TUTORIAL_DRY_SHA}" \ + --event-name=workflow_call \ + --force-targets-json='["control-plane-api"]' \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --application-image-provider=off +``` + +Sanitized expected output contains this package intent and deploy dry-run +summary: + +```text +[package] control-plane-api: oci_image +package_artifact_kind=oci_image +package_artifact_status=planned +package_artifact_image=control-plane-api +package_artifact_platforms=["linux/amd64"] +package_artifact_publication=no-image-or-digest-produced-dry-run +``` + +There is no `package_artifact_repository`, digest, reference, or evidence path +because provider `off` has no destination identity. + +Export the planned package so the manifest itself can be inspected: + +```bash +PLAN_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-provider-off-plan" +test ! -e "${PLAN_DIR}" + +dagger -m "${RUSH_DELIVERY_MODULE}" call \ + build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --artifact-prefix=deploy-target \ + --git-sha="${TUTORIAL_DRY_SHA}" \ + --source-repository-url=https://github.com/example/control-plane.git \ + --dry-run=true \ + --application-image-provider=off \ + export --path="${PLAN_DIR}" + +jq . "${PLAN_DIR}/.dagger/runtime/package-manifest.json" +``` + +Sanitized expected manifest (this is also a complete schema-valid planned v2 +manifest): + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "planned" + } + } +} +``` + +## What Did And Did Not Run + +| Selection | Provider mode | Dry-run behavior | +| --------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| Filesystem-only | `off`, malformed unused file, or no provider file | Provider metadata and OCI credentials are irrelevant. | +| OCI | `off` | Plans relative image, platform, and source revision; no repository. | +| OCI | named provider | Parses that provider and plans its repository; requires and resolves no credential values. | + +The provider-off dry run performs no application-image Docker build, no +destination-registry request, no Syft or Grype execution, no Cosign execution, +no provider credential read, no signing, and no live Deploy script or deploy-tag +side effect. The composed workflow still acquires source, installs dependencies, +runs the normal Rush build, packages a plan, and formats a Deploy dry-run +summary. Source acquisition, module/base-image pulls, dependency installation, +and Rush Build can therefore use the network depending on the selected +entrypoint and cache state. + +A short SHA fails before planning. A live run with an OCI selection and provider +`off` fails before Rush Build/image preparation with an instruction to select a +configured provider. A dry-run failure mentioning Syft, Grype, Cosign, or a +registry request indicates a regression: none belongs to this path. + +## Checkpoint + +```bash +jq -e \ + '.artifacts["control-plane-api"] + | .status == "planned" + and .source_revision == "0123456789abcdef0123456789abcdef01234567" + and (has("repository") | not) + and (has("digest") | not) + and (has("evidence") | not)' \ + "${PLAN_DIR}/.dagger/runtime/package-manifest.json" +``` + +`jq` should print `true` and exit zero. + +Next: [Registry And Cosign Bootstrap](../registry-and-cosign-bootstrap). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/publish-and-inspect.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/publish-and-inspect.md new file mode 100644 index 0000000..7b92585 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/publish-and-inspect.md @@ -0,0 +1,459 @@ +--- +title: "4 - Publish And Inspect" +sidebar_label: "4 - Publish And Inspect" +--- + +This chapter runs the first live side effect: one image publication followed by +Cosign signature/attestation writes and verification. It exports the complete +packaged directory only after every selected target succeeds. + +## Prerequisites + +- Complete [Registry And Cosign Bootstrap](../registry-and-cosign-bootstrap). +- `.dagger/application-images/providers.yaml` contains your literal lowercase + GHCR owner, not `example`. +- `OCI_SECRET_DIR/deploy.env` contains all five selected provider values as + single physical lines; PEMs contain literal `\n` pairs. +- The GHCR token can push the subject and write Cosign's digest-derived `.sig` + and `.att` attachment artifacts. +- The source tree and source URL represent a trusted release revision. + +Commit the provider coordinate so the live SHA identifies the exact inputs: + +```bash +set -euo pipefail + +git add .dagger/application-images/providers.yaml +if ! git diff --cached --quiet; then + git commit -m "chore: configure tutorial GHCR namespace" +fi + +export SOURCE_SHA="$(git rev-parse HEAD)" +test "${#SOURCE_SHA}" -eq 40 +test "$(git status --porcelain)" = "" + +IFS= read -r -p 'Trusted source repository URL (https://...git): ' \ + SOURCE_REPOSITORY_URL +[[ ${SOURCE_REPOSITORY_URL} == https://*.git ]] +[[ ${SOURCE_REPOSITORY_URL} != *[[:space:]]* ]] +node -e ' +const url = new URL(process.argv[1]); +if ( + url.protocol !== "https:" || + !url.hostname || + !url.pathname.endsWith(".git") || + url.username || + url.password || + url.search || + url.hash +) process.exit(1); +' "${SOURCE_REPOSITORY_URL}" + +export DEPLOY_ENV_FILE="${OCI_SECRET_DIR}/deploy.env" +test -f "${DEPLOY_ENV_FILE}" +test "$(stat -c '%a' "${DEPLOY_ENV_FILE}")" = 600 +``` + +If the provider file is already committed, the conditional skips the commit; +the clean-status assertion is still mandatory. On non-GNU systems, replace the +`stat` check with the platform's equivalent and require owner-only read/write +permissions. + +## Primary Live Flow From A Clean Checkout + +Use `build-and-package-deploy-targets` from a clean checkout. It carries the +normal Rush build output into Package inside Dagger, then exports the final +workspace once: + +```bash +export PACKAGE_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-package-${SOURCE_SHA}" +test ! -e "${PACKAGE_DIR}" + +dagger -m "${RUSH_DELIVERY_MODULE}" call \ + build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --artifact-prefix=deploy-target \ + --deploy-env-file="${DEPLOY_ENV_FILE}" \ + --dry-run=false \ + --git-sha="${SOURCE_SHA}" \ + --source-repository-url="${SOURCE_REPOSITORY_URL}" \ + --application-image-provider=ghcr \ + export --path="${PACKAGE_DIR}" +``` + +The observable ordering is deliberate: + +1. select and validate provider metadata plus protected credential names before + project-controlled Build, without reading credential values; +2. run the normal Rush Build; +3. when Package starts, resolve only the selected provider's live values into + framework-owned Dagger secrets, then materialize every selected filesystem + package validation/command without projecting those secrets; +4. cryptographically preflight the provider key pair; +5. prepare every OCI Docker build, SPDX SBOM, and Grype policy result; +6. only after both filesystem and OCI preparation succeed, publish targets one + at a time in stable selected-target order; +7. for each published digest, sign, attach SPDX and provenance attestations, + verify all three, and create local evidence records; +8. write the successful manifest only after the batch finishes. + +Preparation can run concurrently. Publication is ordered and nontransactional. +If a post-publish Cosign step fails, Rush Delivery reports the canonical digest +that may remain, writes no successful manifest, and does not start Deploy. + +Rush Delivery pins `--new-bundle-format=false` on the six registry Cosign +commands. With the pinned Cosign `3.1.2`, one `.sig` attachment stores the +signature and one shared `.att` image stores both attestation predicates. The +OCI 1.1 Referrers API is not used. GHCR may retain an untagged superseded `.att` +version after the second attestation write, so package-version counts can exceed +the two current attachments. The three real Cosign verification commands—not a +UI count—prove completeness. + +Sanitized expected output: + +```text +## Build and package deploy targets +... Rush Build ... +[package] control-plane-api: oci_image +... Cosign provider preflight ... +... Docker image build / SPDX / Grype preparation ... +... registry publication / sign / attest / verify ... +Directory exported to /tmp/rush-delivery-oci-package- +``` + +Logs and wording can vary with Dagger progress output. They must not contain the +registry token, signing password, private PEM, or generated Docker auth JSON. + +## Standalone Package Requires A Restored Build + +Use `package-deploy-targets` only after exporting and restoring the complete +built directory. The following is the supported split, not an alternative that +skips Build: + +The restored built directory is a trusted Package input. Standalone Package +freezes the provider credential names present at this point; it cannot recover +the pre-Build boundary if an independently run Build changed provider or Deploy +metadata. Keep the Build output immutable and access-controlled, or prefer +`build-and-package-deploy-targets`, which captures that boundary before Build. + +```bash +BUILT_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-built-${SOURCE_SHA}" +STANDALONE_PACKAGE_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-standalone-${SOURCE_SHA}" +test ! -e "${BUILT_DIR}" +test ! -e "${STANDALONE_PACKAGE_DIR}" + +dagger -m "${RUSH_DELIVERY_MODULE}" call build-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --dry-run=false \ + export --path="${BUILT_DIR}" + +test -f "${BUILT_DIR}/apps/control-plane-api/dist/payload.txt" + +dagger -m "${RUSH_DELIVERY_MODULE}" call package-deploy-targets \ + --repo="${BUILT_DIR}" \ + --ci-plan-file="${BUILT_DIR}/ci/oci-plan.json" \ + --artifact-prefix=deploy-target \ + --git-sha="${SOURCE_SHA}" \ + --source-repository-url="${SOURCE_REPOSITORY_URL}" \ + --dry-run=false \ + --deploy-env-file="${DEPLOY_ENV_FILE}" \ + --application-image-provider=ghcr \ + export --path="${STANDALONE_PACKAGE_DIR}" +``` + +Running the second command against the original clean checkout should fail its +Docker build because `dist/payload.txt` is intentionally generated. That failure +means the split-stage handoff lost build output. + +## Inspect The Export + +```bash +find "${PACKAGE_DIR}/.dagger/runtime" -type f -print | LC_ALL=C sort +jq . "${PACKAGE_DIR}/.dagger/runtime/package-manifest.json" + +jq -e \ + '.artifacts["control-plane-api"].reference + | test("@sha256:[a-f0-9]{64}$")' \ + "${PACKAGE_DIR}/.dagger/runtime/package-manifest.json" + +jq -e \ + '.artifacts["control-plane-api"] + | .status == "published" + and .evidence.signature.verified == true + and .digest == .evidence.sbom.subject_digest + and .digest == .evidence.provenance.subject_digest' \ + "${PACKAGE_DIR}/.dagger/runtime/package-manifest.json" +``` + +Sanitized expected file list: + +```text +.../.dagger/runtime/application-image-credential-capability.json +.../.dagger/runtime/evidence/control-plane-api/provenance.json +.../.dagger/runtime/evidence/control-plane-api/sbom.spdx.json +.../.dagger/runtime/evidence/control-plane-api/scan.json +.../.dagger/runtime/package-manifest.json +``` + +The internal capability contains only provider, credential-field, and +environment-variable names frozen by Package. Keep it with the complete bundle; +do not edit or selectively copy it. + +Prove that secret values are absent without putting them in process arguments or +printing a match. The public GHCR owner is expected in repository coordinates, +and the authentication username is non-secret and may equal that owner, so this +check covers the token, encrypted private PEM, password, and public PEM instead: + +```bash +python3 - \ + "${PACKAGE_DIR}" \ + "${OCI_SECRET_DIR}/ghcr-token.txt" \ + "${OCI_SECRET_DIR}/cosign.key" \ + "${OCI_SECRET_DIR}/cosign.key.flat" \ + "${OCI_SECRET_DIR}/cosign-password.txt" \ + "${OCI_SECRET_DIR}/cosign.pub" \ + "${OCI_SECRET_DIR}/cosign.pub.flat" <<'PY' +from pathlib import Path +import sys + +bundle = Path(sys.argv[1]) +sentinels = [Path(name).read_bytes() for name in sys.argv[2:]] +sentinels = [value for value in sentinels if value] + +for path in bundle.rglob("*"): + if not path.is_file(): + continue + contents = path.read_bytes() + if any(value in contents for value in sentinels): + raise SystemExit(f"credential material found in bundle file: {path}") +PY +``` + +Any hit is a release-blocking credential leak. Quarantine the bundle, rotate +exposed values, and investigate before retrying. + +## Complete Manifest Examples + +These are complete, sanitized, schema-valid examples. They use synthetic values +and are not excerpts from your publication. + +### Provider-off planned manifest + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "planned" + } + } +} +``` + +### Published manifest + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "evidence": { + "provenance": { + "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "format": "slsa-provenance-v1", + "path": ".dagger/runtime/evidence/control-plane-api/provenance.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "sbom": { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "format": "spdx-json", + "path": ".dagger/runtime/evidence/control-plane-api/sbom.spdx.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "scan": { + "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "path": ".dagger/runtime/evidence/control-plane-api/scan.json", + "policy": ["high", "critical"], + "result": "passed", + "scanner": "grype-0.116.1" + }, + "signature": { + "kind": "sigstore", + "reference": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "verified": true + } + }, + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "reference": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "repository": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api", + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "published" + } + } +} +``` + +### Mixed v2 manifest + +```json +{ + "schema_version": "rush-delivery-package-manifest/v2", + "artifacts": { + "control-plane-api": { + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "evidence": { + "provenance": { + "digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "format": "slsa-provenance-v1", + "path": ".dagger/runtime/evidence/control-plane-api/provenance.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "sbom": { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "format": "spdx-json", + "path": ".dagger/runtime/evidence/control-plane-api/sbom.spdx.json", + "subject_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "scan": { + "digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "path": ".dagger/runtime/evidence/control-plane-api/scan.json", + "policy": ["high", "critical"], + "result": "passed", + "scanner": "grype-0.116.1" + }, + "signature": { + "kind": "sigstore", + "reference": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "verified": true + } + }, + "image": "control-plane-api", + "kind": "oci_image", + "platforms": ["linux/amd64"], + "reference": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "repository": "ghcr.io/acme/rush-delivery-tutorial/control-plane-api", + "source_revision": "0123456789abcdef0123456789abcdef01234567", + "status": "published" + }, + "webapp": { + "deploy_path": "apps/webapp/dist", + "kind": "directory", + "path": "apps/webapp/dist" + } + } +} +``` + +For an OCI artifact: + +- `repository` is the normalized registry path, without tag or digest; +- `reference` is the only deployment identity and equals + `repository@sha256:...`; +- `digest` is the image manifest digest returned by the registry; +- `source_revision` is the full source SHA used for the image label and Deploy + preflight; +- `platforms` contains the one selected platform; +- document `digest` values hash the local evidence files, while each + `subject_digest` binds SBOM/provenance to the image digest; +- scan evidence records the exact rejected-severity set and local Grype result; +- `signature.reference` repeats the verified digest-bound subject, and + `verified: true` records successful Package-time key-backed verification. + +Rush Delivery also pushes the navigation tag `sha-` during the +single publication call, but does not record or deploy that tag. The canonical +manifest reference remains digest-only. The signature is stored in the +digest-derived `.sig` attachment, while the SPDX and provenance predicates share +the current `.att` attachment. The local Grype report is evidence only and is +not presented as a registry scan attestation. + +## Evidence Excerpts + +The next blocks are meaningful sanitized fragments, not complete documents and +not schema examples. + +SPDX 2.3 fragment: + +```json +{ + "spdxVersion": "SPDX-2.3", + "SPDXID": "SPDXRef-DOCUMENT", + "dataLicense": "CC0-1.0", + "name": "oci-dir:...", + "packages": [ + { "SPDXID": "SPDXRef-DocumentRoot-File-payload.txt", "name": "payload.txt" } + ] +} +``` + +Grype fragment for the minimal `scratch` subject: + +```json +{ + "matches": [], + "descriptor": { "name": "grype", "version": "0.116.1" } +} +``` + +SLSA provenance fragment: + +```json +{ + "buildDefinition": { + "externalParameters": { + "context": "apps/control-plane-api", + "dockerfile": "apps/control-plane-api/Dockerfile", + "image": "control-plane-api", + "platform": "linux/amd64" + }, + "resolvedDependencies": [ + { + "digest": { "gitCommit": "0123456789abcdef0123456789abcdef01234567" }, + "uri": "https://github.com/acme/control-plane.git" + } + ] + } +} +``` + +## Failure Meaning + +- A key-preflight failure names only the provider and failed credential role; + fix the PEM/password/key-pair before retrying. +- A Docker, SPDX, Grype, or filesystem-package preparation failure occurs before + every OCI publish in the batch. +- A registry publication failure means no successful manifest exists; inspect + registry state before retrying because transport failure can be ambiguous. +- A sign/attest/verify failure names the already-published canonical reference; + clean or retain it according to policy before a deliberate retry. +- A missing export means Dagger did not produce the trusted package bundle even + if a registry side effect may have occurred. + +## Checkpoint + +```bash +MANIFEST="${PACKAGE_DIR}/.dagger/runtime/package-manifest.json" +jq -e --arg sha "${SOURCE_SHA}" \ + '.schema_version == "rush-delivery-package-manifest/v2" + and .artifacts["control-plane-api"].status == "published" + and .artifacts["control-plane-api"].source_revision == $sha + and (.artifacts["control-plane-api"].reference + | test("@sha256:[a-f0-9]{64}$"))' \ + "${MANIFEST}" + +test -f "${PACKAGE_DIR}/.dagger/runtime/evidence/control-plane-api/sbom.spdx.json" +test -f "${PACKAGE_DIR}/.dagger/runtime/evidence/control-plane-api/scan.json" +test -f "${PACKAGE_DIR}/.dagger/runtime/evidence/control-plane-api/provenance.json" +``` + +Every command must exit zero before the bundle is eligible for Deploy. + +Next: [Deploy The Digest](../deploy-the-digest). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/registry-and-cosign-bootstrap.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/registry-and-cosign-bootstrap.md new file mode 100644 index 0000000..e7b10a8 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/registry-and-cosign-bootstrap.md @@ -0,0 +1,464 @@ +--- +title: "3 - Registry And Cosign Bootstrap" +sidebar_label: "3 - Registry And Cosign Bootstrap" +--- + +This chapter configures one GHCR destination and one password-protected Cosign +key pair. It does not publish an image. + +## Prerequisites + +- Complete [Provider-Off Dry Run](../provider-off-dry-run). +- Own or administer a GitHub repository and a GHCR namespace that may receive + `ghcr.io//rush-delivery-tutorial/control-plane-api`. +- Install `gh` and authenticate it to the target GitHub repository. +- Prefer an installed Cosign `3.1.2`; a pinned-container workstation option is + included below. +- Create or select a protected GitHub environment named `production` before + storing its secrets. + +The GHCR owner and every repository component must be lowercase and normalized. +Provider metadata is literal YAML: it does not interpolate shell variables, +GitHub expressions, or `${NAME}` placeholders. + +## Review The Complete Provider Template + +The checked-in +[`providers.yaml`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/.dagger/application-images/providers.yaml) +is complete and schema-valid, but `example/...` is intentionally not a pushable +tutorial destination: + +```yaml +# GHCR tutorial template: replace "example" with a normalized owner before use. +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +providers: + ghcr: + kind: oci_registry + registry: ghcr.io + repository_prefix: example/rush-delivery-tutorial + username_env: RD_OCI_GHCR_USERNAME + token_env: RD_OCI_GHCR_TOKEN + signing_key_env: RD_OCI_COSIGN_PRIVATE_KEY + signing_password_env: RD_OCI_COSIGN_PASSWORD + verification_key_env: RD_OCI_COSIGN_PUBLIC_KEY +``` + +Replace the template with a literal owner now: + +```bash +set -euo pipefail + +IFS= read -r -p 'Lowercase GHCR owner: ' GHCR_OWNER +[[ ${GHCR_OWNER} =~ ^[a-z0-9]+([._-][a-z0-9]+)*$ ]] +IFS= read -r -p 'GitHub username that authenticates the GHCR token: ' \ + GHCR_USERNAME +test -n "${GHCR_USERNAME}" +[[ ${GHCR_USERNAME} != *[[:space:]]* ]] + +python3 - "${GHCR_OWNER}" \ + .dagger/application-images/providers.yaml <<'PY' +from pathlib import Path +import sys + +owner = sys.argv[1] +path = Path(sys.argv[2]) +source = path.read_text(encoding="utf-8") +needle = "repository_prefix: example/rush-delivery-tutorial" +if source.count(needle) != 1: + raise SystemExit("provider template line was not found exactly once") +path.write_text( + source.replace( + needle, + f"repository_prefix: {owner}/rush-delivery-tutorial", + ), + encoding="utf-8", +) +PY + +grep -F "repository_prefix: ${GHCR_OWNER}/rush-delivery-tutorial" \ + .dagger/application-images/providers.yaml +``` + +If the owner check fails, normalize the account/organization spelling rather +than adding uppercase or an unsupported path to metadata. If validation later +reports an invalid provider, compare the complete file with the +[v0.9.0 provider schema](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/schemas/v0.9.0/application-image-providers.schema.json). +`GHCR_OWNER` is the destination user or organization namespace; +`GHCR_USERNAME` is the user that authenticates the token. They are often +different when publishing to an organization. + +## Generate A Password-Protected Cosign 3.1.2 Key + +### Preferred: installed binary + +Verify the exact installed version, create a private directory outside the Git +repository, and prompt without putting the password in shell history or process +arguments: + +```bash +set -euo pipefail + +cosign version +cosign version 2>&1 | grep -F 'GitVersion: v3.1.2' + +export OCI_SECRET_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-secrets-${USER:-operator}" +test ! -e "${OCI_SECRET_DIR}" +umask 077 +mkdir -m 0700 "${OCI_SECRET_DIR}" + +IFS= read -r -s -p 'New Cosign key password: ' COSIGN_PASSWORD +printf '\n' +test -n "${COSIGN_PASSWORD}" +[[ ${COSIGN_PASSWORD} != [[:space:]]* ]] +[[ ${COSIGN_PASSWORD} != *[[:space:]] ]] +[[ ${COSIGN_PASSWORD} != *$'\r'* ]] +[[ ${COSIGN_PASSWORD} != *$'\n'* ]] +export COSIGN_PASSWORD +cosign generate-key-pair \ + --output-key-prefix "${OCI_SECRET_DIR}/cosign" +printf '%s' "${COSIGN_PASSWORD}" > "${OCI_SECRET_DIR}/cosign-password.txt" +unset COSIGN_PASSWORD +chmod 0600 "${OCI_SECRET_DIR}"/* +``` + +Sanitized expected output: + +```text +GitVersion: v3.1.2 +Private key written to /tmp/.../cosign.key +Public key written to /tmp/.../cosign.pub +``` + +The whitespace checks reject a password that begins or ends with whitespace, +because the public flat-env record parser trims each physical line. The explicit +CR/LF checks reject line breaks. Internal spaces are allowed. + +### Alternative: digest-pinned container on an operator workstation + +This Docker command is only a one-time operator key-bootstrap option. It is not +part of OCI Package and does not imply that Rush Delivery needs a host Docker +CLI, daemon, or socket. Podman can be substituted with equivalent bind-mount and +environment semantics. + +```bash +set -euo pipefail + +COSIGN_IMAGE='ghcr.io/sigstore/cosign/cosign@sha256:d91bc4e7e95e8d2f549c747a72dc174f90579e410a1695f57f686674f84ce849' +export OCI_CONTAINER_KEY_DIR="${TMPDIR:-/tmp}/rush-delivery-cosign-container-${USER:-operator}" +test ! -e "${OCI_CONTAINER_KEY_DIR}" +umask 077 +mkdir -m 0700 "${OCI_CONTAINER_KEY_DIR}" + +printf '%s\n' \ + 'Cosign will prompt twice. Store that password in your protected password manager.' +docker run --rm --interactive --tty \ + --user="$(id -u):$(id -g)" \ + --mount="type=bind,src=${OCI_CONTAINER_KEY_DIR},dst=/keys" \ + "${COSIGN_IMAGE}" \ + generate-key-pair --output-key-prefix=/keys/cosign + +IFS= read -r -s -p 'Re-enter the same password for the local env file: ' \ + COSIGN_PASSWORD +printf '\n' +test -n "${COSIGN_PASSWORD}" +[[ ${COSIGN_PASSWORD} != [[:space:]]* ]] +[[ ${COSIGN_PASSWORD} != *[[:space:]] ]] +[[ ${COSIGN_PASSWORD} != *$'\r'* ]] +[[ ${COSIGN_PASSWORD} != *$'\n'* ]] +printf '%s' "${COSIGN_PASSWORD}" > \ + "${OCI_CONTAINER_KEY_DIR}/cosign-password.txt" +unset COSIGN_PASSWORD +chmod 0600 "${OCI_CONTAINER_KEY_DIR}"/* +export OCI_SECRET_DIR="${OCI_CONTAINER_KEY_DIR}" +``` + +`--rm` removes the temporary container, the explicit host UID/GID owns the key +files, and Cosign reads the generation password from the interactive terminal. +The password is never placed in the Docker container configuration, command +arguments, or shell history. The later Rush Delivery preflight checks that the +re-entered value decrypts the generated key before any application image is +built or published. Do not use `docker create --env=COSIGN_PASSWORD`; that +persists the password in inspectable container configuration until deletion. + +Use one key-generation path, never both. The container path explicitly aliases +its directory to `OCI_SECRET_DIR`, so every following command is identical for +both choices. + +## Verify Markers And Flatten The PEM Values + +The private key must be encrypted and the public key must match these markers: + +```bash +head -n 1 "${OCI_SECRET_DIR}/cosign.key" +tail -n 1 "${OCI_SECRET_DIR}/cosign.key" +head -n 1 "${OCI_SECRET_DIR}/cosign.pub" +tail -n 1 "${OCI_SECRET_DIR}/cosign.pub" +``` + +Expected output: + +```text +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- +-----BEGIN PUBLIC KEY----- +-----END PUBLIC KEY----- +``` + +Rush Delivery's public flat-env parser accepts one physical line per value. +Convert real newlines to literal backslash-`n` pairs, then prove a byte-for-byte +round trip before using the result: + +```bash +python3 - \ + "${OCI_SECRET_DIR}/cosign.key" \ + "${OCI_SECRET_DIR}/cosign.key.flat" <<'PY' +from pathlib import Path +import sys + +source = Path(sys.argv[1]).read_text(encoding="utf-8") +Path(sys.argv[2]).write_text(source.replace("\n", r"\n"), encoding="utf-8") +PY + +python3 - \ + "${OCI_SECRET_DIR}/cosign.pub" \ + "${OCI_SECRET_DIR}/cosign.pub.flat" <<'PY' +from pathlib import Path +import sys + +source = Path(sys.argv[1]).read_text(encoding="utf-8") +Path(sys.argv[2]).write_text(source.replace("\n", r"\n"), encoding="utf-8") +PY + +python3 - \ + "${OCI_SECRET_DIR}/cosign.key.flat" \ + "${OCI_SECRET_DIR}/cosign.key.roundtrip" <<'PY' +from pathlib import Path +import sys + +flat = Path(sys.argv[1]).read_text(encoding="utf-8") +Path(sys.argv[2]).write_text(flat.replace(r"\n", "\n"), encoding="utf-8") +PY + +python3 - \ + "${OCI_SECRET_DIR}/cosign.pub.flat" \ + "${OCI_SECRET_DIR}/cosign.pub.roundtrip" <<'PY' +from pathlib import Path +import sys + +flat = Path(sys.argv[1]).read_text(encoding="utf-8") +Path(sys.argv[2]).write_text(flat.replace(r"\n", "\n"), encoding="utf-8") +PY + +cmp "${OCI_SECRET_DIR}/cosign.key" \ + "${OCI_SECRET_DIR}/cosign.key.roundtrip" +cmp "${OCI_SECRET_DIR}/cosign.pub" \ + "${OCI_SECRET_DIR}/cosign.pub.roundtrip" +test "$(wc -l < "${OCI_SECRET_DIR}/cosign.key.flat")" -eq 0 +test "$(wc -l < "${OCI_SECRET_DIR}/cosign.pub.flat")" -eq 0 +``` + +The `wc` results are zero because each flat file contains literal `\n` pairs +and no physical newline. Do not paste raw multiline PEM into a public Action +env input: raw multiline acceptance is only an internal normalization test in +`v0.9.0`. + +## Create The Local Flat Env File + +Create a time-bounded GHCR token for a dedicated release/bot identity. For a +classic personal access token this normally means `write:packages`, which also +includes package read; organization policy or SSO may require authorization. +Do not add `delete:packages` to the publishing token. Classic PAT package scopes +are account-wide capabilities, not a token-level restriction to the tutorial +namespace or one package. Constrain effective access with the bot's +organization/package permissions, keep unrelated package access off that +account, record an owner and expiry, and test rotation before revoking the old +token. Do not use a developer's general-purpose PAT. GitHub documents the +package scopes and repository linkage in +[Working with the Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). + +```bash +IFS= read -r -s -p 'GHCR token: ' RD_OCI_GHCR_TOKEN +printf '\n' +test -n "${RD_OCI_GHCR_TOKEN}" +[[ ${RD_OCI_GHCR_TOKEN} != [[:space:]]* ]] +[[ ${RD_OCI_GHCR_TOKEN} != *[[:space:]] ]] +[[ ${RD_OCI_GHCR_TOKEN} != *$'\r'* ]] +[[ ${RD_OCI_GHCR_TOKEN} != *$'\n'* ]] +printf '%s' "${RD_OCI_GHCR_TOKEN}" > \ + "${OCI_SECRET_DIR}/ghcr-token.txt" +unset RD_OCI_GHCR_TOKEN + +printf '%s' "${GHCR_USERNAME}" > "${OCI_SECRET_DIR}/ghcr-username.txt" + +RD_OCI_COSIGN_PRIVATE_KEY="$(<"${OCI_SECRET_DIR}/cosign.key.flat")" +RD_OCI_COSIGN_PUBLIC_KEY="$(<"${OCI_SECRET_DIR}/cosign.pub.flat")" +RD_OCI_COSIGN_PASSWORD="$(<"${OCI_SECRET_DIR}/cosign-password.txt")" +RD_OCI_GHCR_TOKEN="$(<"${OCI_SECRET_DIR}/ghcr-token.txt")" + +{ + printf 'RD_OCI_GHCR_USERNAME=%s\n' "${GHCR_USERNAME}" + printf 'RD_OCI_GHCR_TOKEN=%s\n' "${RD_OCI_GHCR_TOKEN}" + printf 'RD_OCI_COSIGN_PRIVATE_KEY=%s\n' \ + "${RD_OCI_COSIGN_PRIVATE_KEY}" + printf 'RD_OCI_COSIGN_PASSWORD=%s\n' "${RD_OCI_COSIGN_PASSWORD}" + printf 'RD_OCI_COSIGN_PUBLIC_KEY=%s\n' \ + "${RD_OCI_COSIGN_PUBLIC_KEY}" +} > "${OCI_SECRET_DIR}/deploy.env" + +unset RD_OCI_COSIGN_PRIVATE_KEY RD_OCI_COSIGN_PUBLIC_KEY +unset RD_OCI_COSIGN_PASSWORD RD_OCI_GHCR_TOKEN +chmod 0600 "${OCI_SECRET_DIR}/deploy.env" +cut -d= -f1 "${OCI_SECRET_DIR}/deploy.env" +``` + +Expected output is names only: + +```text +RD_OCI_GHCR_USERNAME +RD_OCI_GHCR_TOKEN +RD_OCI_COSIGN_PRIVATE_KEY +RD_OCI_COSIGN_PASSWORD +RD_OCI_COSIGN_PUBLIC_KEY +``` + +The canonical [`.gitignore`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/examples/oci-application-image-rush-repo/.gitignore) +also rejects local generated state and common key/env names: + +```text +common/temp/ +**/.rush/temp/ +**/node_modules/ +**/rush-logs/ +apps/control-plane-api/dist/ +.dagger/runtime/ +.env +.env.* +*.env +*.key +*.pem +*.pub +oci-package/ +oci-package.tar +oci-package.tar.sha256 +``` + +The primary protection is still storing the material outside the repository +with mode `0700`/`0600`; `.gitignore` is only a backstop. + +## Store Protected GitHub Values + +The following forms read values from files and do not echo them or put them in +CLI arguments. Run them from the target GitHub repository or add +`--repo=OWNER/REPOSITORY` to every command. + +```bash +gh secret set RD_OCI_GHCR_TOKEN --env=production \ + < "${OCI_SECRET_DIR}/ghcr-token.txt" +gh secret set RD_OCI_COSIGN_PRIVATE_KEY --env=production \ + < "${OCI_SECRET_DIR}/cosign.key.flat" +gh secret set RD_OCI_COSIGN_PASSWORD --env=production \ + < "${OCI_SECRET_DIR}/cosign-password.txt" +gh variable set RD_OCI_GHCR_USERNAME --env=production \ + < "${OCI_SECRET_DIR}/ghcr-username.txt" +gh secret set RD_OCI_COSIGN_PUBLIC_KEY --env=production \ + < "${OCI_SECRET_DIR}/cosign.pub.flat" +``` + +If a command reports that the environment does not exist or access is denied, +create/authorize the protected `production` environment and retry. Do not fall +back to unprotected repository secrets for a live release merely to bypass an +environment gate. + +## Select GHCR In A Credential-Free Dry Run + +The named-provider dry run parses the provider and constructs its literal +repository, but deliberately does not read or cryptographically preflight any +key/token value: + +```bash +export TUTORIAL_DRY_SHA="0123456789abcdef0123456789abcdef01234567" +NAMED_PLAN_DIR="${TMPDIR:-/tmp}/rush-delivery-oci-ghcr-plan" +test ! -e "${NAMED_PLAN_DIR}" + +dagger -m "${RUSH_DELIVERY_MODULE}" call \ + build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file=ci/oci-plan.json \ + --artifact-prefix=deploy-target \ + --git-sha="${TUTORIAL_DRY_SHA}" \ + --source-repository-url=https://github.com/example/control-plane.git \ + --dry-run=true \ + --application-image-provider=ghcr \ + export --path="${NAMED_PLAN_DIR}" + +jq -r '.artifacts["control-plane-api"].repository' \ + "${NAMED_PLAN_DIR}/.dagger/runtime/package-manifest.json" +``` + +Expected output: + +```text +ghcr.io//rush-delivery-tutorial/control-plane-api +``` + +If the output literally contains `example`, stop: the template has not been +made pushable. A request for any `RD_OCI_*` value during this dry run is a +failure of the no-credential contract. + +## Credential Lifecycle And Trust Boundary + +The five roles are distinct: + +- username and token authenticate the subject publish plus Cosign's + digest-derived signature/attestation attachment-tag writes; +- the encrypted private key signs the digest and two attestations; +- the password decrypts that private key only inside framework-owned Cosign + execution; +- the public key verifies the private-key match and published objects. + +Their environment names are also a validated part of the boundary: every one +must be globally unique across every provider in the file. Never reuse a token, +key, or password name as `username_env`; the registry username is intentionally +non-secret because Dagger's registry-auth call graph may display it. + +Live Package performs an offline cryptographic preflight once per selected +provider before application-image build/publication: derive the public key, +sign a fixed challenge, and verify it with both the derived and configured +public keys. Dagger may need to pull the pinned Cosign container first. Registry +authentication itself cannot always be proven without a destination-registry +operation, so a later publication can still fail for permissions or endpoint +policy. + +Rotate a token independently of the signing key. When rotating the signing key, +publish the new public key through reviewed configuration and retain every old +public key for at least as long as any image/bundle signed by it remains +deployable. Loss of the private key prevents new signatures but should not +invalidate retained releases; loss of an old public key prevents independent +verification of those releases. The v2 manifest records that the configured key +verified the digest-bound objects at Package time, but does not record a key +fingerprint, Rekor entry, certificate identity, or public-transparency proof. +Keep key identity/rotation records in the release control plane. + +After secrets are stored and backed up according to policy, remove expendable +workstation copies with explicit paths. For example, inspect +`printf '%s\n' "${OCI_SECRET_DIR}"` first, then use +`find "${OCI_SECRET_DIR}" -type f -delete` and `rmdir "${OCI_SECRET_DIR}"`. +File deletion is not guaranteed secure erasure on copy-on-write or journaled +storage; use managed secret storage for production keys. + +## Checkpoint + +```bash +dagger -m "${RUSH_DELIVERY_MODULE}" call validate-metadata-contract \ + --repo=. >/dev/null +jq -e --arg repository \ + "ghcr.io/${GHCR_OWNER}/rush-delivery-tutorial/control-plane-api" \ + '.artifacts["control-plane-api"] + | .status == "planned" and .repository == $repository + and (has("digest") | not) and (has("evidence") | not)' \ + "${NAMED_PLAN_DIR}/.dagger/runtime/package-manifest.json" +``` + +Both commands should exit zero without reading `deploy.env`. + +Next: [Publish And Inspect](../publish-and-inspect). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/split-stages-and-rollback.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/split-stages-and-rollback.md new file mode 100644 index 0000000..7c6c542 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/oci-application-images/split-stages-and-rollback.md @@ -0,0 +1,529 @@ +--- +title: "7 - Split Stages And Rollback" +sidebar_label: "7 - Split Stages And Rollback" +--- + +This chapter persists the complete packaged directory across jobs, verifies it +against protected release metadata, deploys it, and repeats the process with an +earlier trusted bundle for rollback. Persisting only +`.dagger/runtime/package-manifest.json` is unsafe and incomplete. + +## Prerequisites + +- Complete [GitHub Actions](../github-actions). +- Use immutable-by-ID, access-controlled CI artifact storage with deletion and + retention restricted to release operators. +- Use protected package/deploy jobs and keep GHCR/signing credentials out of + the deploy job. +- Install GNU `tar`, `sha256sum`, `jq`, and Python 3.12 or newer in the handoff + environment. +- Maintain a protected release record outside the unsigned package bundle. For + every bundle it must retain the artifact ID, producing workflow run ID, + archive file name, independently computed SHA-256, and original full source + SHA. + +The artifact service protects the immutable stored object; the external record +selects and authenticates the expected object. Do not derive the expected +checksum or expected source SHA from the downloaded archive itself. +The upload/download steps below follow Chapter 6's production policy: each +third-party action is pinned to a reviewed full commit SHA with its release +version retained as a comment. + +## Detect, Build, Publish, And Export + +Run these commands from the exact committed source revision: + +```bash +set -euo pipefail + +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export SOURCE_SHA="$(git rev-parse HEAD)" +test "${#SOURCE_SHA}" -eq 40 +test "$(git status --porcelain)" = "" + +SPLIT_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/rush-delivery-oci-split.XXXXXX")" +CI_PLAN_FILE="${SPLIT_ROOT}/ci-plan.json" +PACKAGED_DIR="${SPLIT_ROOT}/packaged" + +dagger -m "${RUSH_DELIVERY_MODULE}" call detect \ + --repo=. \ + --event-name=workflow_call \ + --force-targets-json='["control-plane-api"]' \ + --deploy-tag-prefix=deploy/prod \ + > "${CI_PLAN_FILE}" + +jq -e \ + '.mode == "release" + and .deploy_targets == ["control-plane-api"]' \ + "${CI_PLAN_FILE}" + +dagger -m "${RUSH_DELIVERY_MODULE}" call \ + build-and-package-deploy-targets \ + --repo=. \ + --ci-plan-file="${CI_PLAN_FILE}" \ + --artifact-prefix=deploy-target \ + --deploy-env-file="${DEPLOY_ENV_FILE}" \ + --dry-run=false \ + --git-sha="${SOURCE_SHA}" \ + --source-repository-url="${SOURCE_REPOSITORY_URL}" \ + --application-image-provider=ghcr \ + export --path="${PACKAGED_DIR}" +``` + +Sanitized expected plan: + +```json +{ + "affected_projects_by_deploy_target": { + "control-plane-api": [] + }, + "deploy_targets": ["control-plane-api"], + "mode": "release", + "pr_base_sha": "", + "release_targets": [], + "validate_targets": [] +} +``` + +Forced selection is explicit; `affected_projects_by_deploy_target` may differ +when a prior deploy tag exists. A Detect failure usually means Git history/tags +were omitted from source, the forced target is unknown, or metadata is invalid. +A Package failure has the same pre-/post-publication meaning described in +[Publish And Inspect](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/docs/tutorial/oci-application-images/04-publish-and-inspect.md#failure-meaning). + +## Archive The Complete Directory + +Archive the directory—not just the manifest—with a POSIX tar stream that keeps +normal mode bits and safe symlinks. The deterministic flags also make repeated +archives of identical bytes easier to audit: + +```bash +ARCHIVE_NAME="rush-delivery-oci-package-${SOURCE_SHA}.tar.gz" +ARCHIVE_PATH="${SPLIT_ROOT}/${ARCHIVE_NAME}" +CHECKSUM_PATH="${ARCHIVE_PATH}.sha256" + +tar \ + --create \ + --gzip \ + --format=posix \ + --sort=name \ + --mtime=@0 \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + --pax-option=delete=atime,delete=ctime \ + --file="${ARCHIVE_PATH}" \ + --directory="${PACKAGED_DIR}" \ + . + +ARCHIVE_SHA256="$(sha256sum "${ARCHIVE_PATH}" | awk '{print $1}')" +[[ ${ARCHIVE_SHA256} =~ ^[a-f0-9]{64}$ ]] +printf '%s %s\n' "${ARCHIVE_SHA256}" "${ARCHIVE_NAME}" \ + > "${CHECKSUM_PATH}" + +tar --list --gzip --file="${ARCHIVE_PATH}" \ + | grep -F './.dagger/runtime/package-manifest.json' +tar --list --gzip --file="${ARCHIVE_PATH}" \ + | grep -F './.dagger/runtime/application-image-credential-capability.json' +``` + +Package writes the second internal file after Build. It contains credential +names, provider names, and field roles—not values—and preserves the pre-Build +projection boundary for standalone Deploy. Older bundles without it fall back +to their provider file. No credential values belong in either bundle. The +repository's [`split-stage handoff test`](https://github.com/BootstrapLaboratory/rush-delivery/blob/v0.9.0/test/split-stage-handoff.test.ts) +exercises +mode and symlink preservation plus path/link escape rejection for this archive +shape. + +Expected output includes both required paths. If either is absent, do not upload +the archive. A checksum file placed beside the archive is convenient for manual +transport, but it is not the trusted expected checksum: the protected release +record must store `ARCHIVE_SHA256` independently. + +Before archiving, also enforce the `v0.9.0` framework-directory shape: + +```bash +for framework_directory in \ + ".dagger" \ + ".dagger/runtime" \ + ".dagger/runtime/evidence" +do + if [[ ! -d "${PACKAGED_DIR}/${framework_directory}" || \ + -L "${PACKAGED_DIR}/${framework_directory}" ]]; then + printf 'invalid packaged framework directory: %s\n' \ + "${framework_directory}" >&2 + exit 1 + fi +done +``` + +Ordinary safe symlinks elsewhere in the complete directory remain supported. +These three paths are framework-owned: Package preserves non-runtime Build +outputs such as `.dagger/generated-output`, clears any pre-existing runtime +file, directory, or symlink, and writes fresh runtime metadata/evidence under +concrete directories. Deploy checks the same boundary before both dry and live +target execution. + +If an older retained bundle fails this gate, do not replace the symlink by hand, +copy only `.dagger/runtime`, or recompute a checksum over the patched archive. +Run the `v0.9.0` Package producer again from the intended source and built +output, export the complete returned directory, and register a new archive, +checksum/identity, and source-SHA record. For OCI, this is a new controlled +Package/publication attempt: inspect and govern registry side effects exactly as +you would for any other new release candidate. + +## Upload By Immutable Artifact ID + +In GitHub Actions, use the current direct-file upload mode so the already-gzipped +tar bytes are not hidden behind a second archive. This exact step follows the +archive command above: + +```yaml +- id: bundle + name: Archive complete package directory + shell: bash + run: | + set -euo pipefail + ARCHIVE_NAME="rush-delivery-oci-package-${GITHUB_SHA}.tar.gz" + ARCHIVE_PATH="${RUNNER_TEMP}/${ARCHIVE_NAME}" + + tar \ + --create \ + --gzip \ + --format=posix \ + --sort=name \ + --mtime=@0 \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + --pax-option=delete=atime,delete=ctime \ + --file="${ARCHIVE_PATH}" \ + --directory="${RUNNER_TEMP}/oci-package" \ + . + + ARCHIVE_SHA256="$(sha256sum "${ARCHIVE_PATH}" | awk '{print $1}')" + [[ ${ARCHIVE_SHA256} =~ ^[a-f0-9]{64}$ ]] + printf 'archive-name=%s\n' "${ARCHIVE_NAME}" >> "${GITHUB_OUTPUT}" + printf 'archive-path=%s\n' "${ARCHIVE_PATH}" >> "${GITHUB_OUTPUT}" + printf 'archive-sha256=%s\n' "${ARCHIVE_SHA256}" >> "${GITHUB_OUTPUT}" + printf 'source-sha=%s\n' "${GITHUB_SHA}" >> "${GITHUB_OUTPUT}" + +- id: upload + name: Upload immutable package archive + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: ${{ steps.bundle.outputs.archive-path }} + archive: false + if-no-files-found: error + overwrite: false + retention-days: 30 + +- name: Emit protected-release record fields + shell: bash + env: + ARCHIVE_NAME: ${{ steps.bundle.outputs.archive-name }} + ARCHIVE_SHA256: ${{ steps.bundle.outputs.archive-sha256 }} + ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }} + ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }} + SOURCE_SHA: ${{ steps.bundle.outputs.source-sha }} + run: | + set -euo pipefail + jq -n \ + --arg archive_name "${ARCHIVE_NAME}" \ + --arg archive_sha256 "${ARCHIVE_SHA256}" \ + --arg artifact_id "${ARTIFACT_ID}" \ + --arg artifact_url "${ARTIFACT_URL}" \ + --arg run_id "${GITHUB_RUN_ID}" \ + --arg source_sha "${SOURCE_SHA}" \ + '{ + archive_name: $archive_name, + archive_sha256: $archive_sha256, + artifact_id: $artifact_id, + artifact_url: $artifact_url, + producing_run_id: $run_id, + source_sha: $source_sha + }' > "${RUNNER_TEMP}/protected-release-record.json" + cat "${RUNNER_TEMP}/protected-release-record.json" +``` + +`archive: false` stores the one tarball as the artifact; its file name is the +artifact name. Copy the emitted JSON fields into an append-only or versioned +release record controlled outside the bundle (for example, an approved change +record/deployment database). The JSON printed in the job is a handoff candidate, +not protection by itself. The protected system must preserve history for +rollback, restrict replacement/deletion, and bind approval to the exact artifact +ID, producing run, checksum, and source SHA. + +`retention-days: 30` is an explicit tutorial placeholder, not a production +retention recommendation. Set it to at least the approved rollback/audit window +and verify that the repository or organization artifact-retention maximum +permits that value. Artifact URLs and IDs stop being usable when the artifact, +run, or repository is deleted. If the required window exceeds GitHub Actions +retention, copy the already checksummed tarball to longer-lived immutable, +access-controlled object storage and record that storage object's immutable ID +alongside the same checksum and source SHA. See the upload action's +[retention and output contract](https://github.com/actions/upload-artifact/blob/main/README.md#inputs). + +Do not upload the raw package directory and assume the artifact service will +preserve executable modes or symlinks. Only the tarball is the portable object. + +## Download And Verify Before Extraction + +At deploy time, an approved operator/control plane supplies these values from +the protected record, not from the bundle: + +```text +PACKAGE_ARTIFACT_ID +PACKAGE_PRODUCING_RUN_ID +PACKAGE_ARCHIVE_NAME +EXPECTED_ARCHIVE_SHA256 +EXPECTED_SOURCE_SHA +``` + +The GitHub download step can select the immutable artifact ID from its original +run and retain the raw tarball: + +```yaml +- name: Download selected package archive + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + artifact-ids: ${{ inputs.package_artifact_id }} + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ inputs.package_producing_run_id }} + path: ${{ runner.temp }}/package-download + skip-decompress: true + digest-mismatch: error +``` + +The deploy job needs `actions: read` and `contents: read`, uses the protected +release environment, and does not need `packages: write` or signing secrets. + +Verify the independent checksum, reject absolute/traversing members and unsafe +links/special files, extract into a sibling staging directory, then atomically +rename it. Python 3.12's `tarfile.data_filter` performs the path/link/type +filtering while retaining ordinary executable bits and safe symlinks: + +```bash +set -euo pipefail + +DOWNLOAD_DIR="${RUNNER_TEMP}/package-download" +ARCHIVE_PATH="${DOWNLOAD_DIR}/${PACKAGE_ARCHIVE_NAME}" +RESTORE_PARENT="${RUNNER_TEMP}/rush-delivery-restored" +RESTORED_DIR="${RESTORE_PARENT}/package" + +test -f "${ARCHIVE_PATH}" +python3 -c 'import sys; assert sys.version_info >= (3, 12)' + +python3 - \ + "${ARCHIVE_PATH}" \ + "${EXPECTED_ARCHIVE_SHA256}" \ + "${RESTORED_DIR}" <<'PY' +from __future__ import annotations + +import hashlib +import os +from pathlib import Path +import shutil +import sys +import tarfile +import tempfile + +archive = Path(sys.argv[1]) +expected = sys.argv[2] +destination = Path(sys.argv[3]) + +if len(expected) != 64 or any(ch not in "0123456789abcdef" for ch in expected): + raise SystemExit("expected archive SHA-256 is not 64 lowercase hex characters") + +digest = hashlib.sha256() +with archive.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) +if digest.hexdigest() != expected: + raise SystemExit("package archive checksum does not match protected release metadata") + +if destination.exists() or destination.is_symlink(): + raise SystemExit("atomic restore destination already exists") + +destination.parent.mkdir(parents=True, exist_ok=True) +staging = Path( + tempfile.mkdtemp(prefix=".package-restore-", dir=destination.parent) +) + +try: + with tarfile.open(archive, mode="r:gz") as bundle: + bundle.extractall(path=staging, filter="data") + os.replace(staging, destination) +except BaseException: + shutil.rmtree(staging, ignore_errors=True) + raise +PY + +test -x "${RESTORED_DIR}/deploy/consume-image.sh" +test -f "${RESTORED_DIR}/.dagger/runtime/package-manifest.json" +test -f "${RESTORED_DIR}/.dagger/runtime/application-image-credential-capability.json" +test -f "${RESTORED_DIR}/.dagger/application-images/providers.yaml" +``` + +`filter="data"` rejects archive members that escape by absolute/`..` path, +symlink or hardlink, and rejects device/FIFO entries. Extraction happens only +after checksum verification. Atomic rename prevents Deploy from observing a +partially restored tree. A checksum failure, filter exception, existing +destination, missing executable, manifest, frozen credential capability, or +provider file is a hard stop—do not fall back to unfiltered `tar -x`. Requiring +the generated capability here ensures a `v0.9.0` restore cannot silently take +the legacy provider-file fallback. + +## Verify The Independently Recorded Source SHA + +Before calling Dagger, compare every restored OCI artifact with the independently +recorded SHA: + +```bash +MANIFEST="${RESTORED_DIR}/.dagger/runtime/package-manifest.json" +[[ ${EXPECTED_SOURCE_SHA} =~ ^[a-f0-9]{40}$ ]] + +jq -e --arg sha "${EXPECTED_SOURCE_SHA}" ' + .schema_version == "rush-delivery-package-manifest/v2" + and ([.artifacts[] | select(.kind == "oci_image")] | length > 0) + and ([.artifacts[] | select(.kind == "oci_image") | .source_revision] + | all(. == $sha)) + and ([.artifacts[] | select(.kind == "oci_image") | .status] + | all(. == "published")) +' "${MANIFEST}" +``` + +If this fails, the bundle and protected source identity disagree. Do not edit +the manifest or change the expected SHA to make the check pass. + +## Deploy The Restored Bundle + +The exact standalone deploy command uses the external SHA and restored package +directory. It reads the frozen provider credential names for the projection +guard, but no registry/signing values are supplied: + +```bash +dagger -m "${RUSH_DELIVERY_MODULE}" call deploy-release \ + --repo="${RESTORED_DIR}" \ + --git-sha="${EXPECTED_SOURCE_SHA}" \ + --release-targets-json='["control-plane-api"]' \ + --environment=prod \ + --dry-run=false \ + --toolchain-image-provider=off \ + --package-manifest-file="${MANIFEST}" +``` + +Rush Delivery re-parses the strict manifest and independently re-hashes the +selected target's SBOM, scan, and provenance before the first deploy wave. It +then mounts only that target's evidence and passes the recorded digest reference +unchanged. It does not query GHCR or rerun Cosign. + +Sanitized expected output: + +```text +control-plane-api accepted immutable image: ghcr.io//rush-delivery-tutorial/control-plane-api@sha256: +``` + +Standalone split-stage Deploy does not update deploy tags. If tag movement is a +required control-plane signal, use the composed Git-source `workflow` or a +separate protected action after successful deployment. Never make a mutable tag +the pull identity. + +## Roll Back To An Earlier Trusted Bundle + +Rollback is the same verified deployment with an earlier record: + +1. Select the earlier approved artifact ID, producing run, archive name, + checksum, and original full SHA from protected history. +2. Download that exact artifact ID; do not select “latest” by name. +3. Verify the externally recorded checksum before safe, atomic extraction. +4. Verify every OCI `source_revision` equals the independently recorded earlier + SHA. +5. Run `deploy-release` with that earlier SHA and unchanged manifest. +6. Confirm the deploy result's `artifactReference` equals the earlier + `repository@sha256:...`; do not rebuild, edit the manifest, or resolve its + `sha-...` navigation tag. + +The same shell block and Dagger command above are the complete rollback +procedure after replacing the five protected-record inputs with the earlier +record. The target platform must still be able to pull that digest, and the old +public verification key/evidence must remain retained even though Deploy does +not rerun registry Cosign verification. + +The default all-in-one Rush Delivery Action returns a workflow result; it does +not automatically retain this reusable packaged directory. A rollback that +depends on bundle evidence therefore needs the explicit stage-level export, +tarball, checksum, artifact upload, and protected record shown here. + +## Retention, Retry, And Cleanup + +Coordinate these lifecycles: + +- retain registry subject digests and their signature/combined-attestation + attachments for at least as long as any environment can deploy or roll them + back; +- retain package archives, protected records, and old public keys for the same + rollback/audit window; +- treat `sha-` as navigation only; tag deletion/movement must not + delete the subject or Cosign attachments needed by retained records; +- keep target-platform pull identity valid for every retained private digest; +- make project deploy scripts idempotent before automatically retrying Deploy; +- retry pre-publication preparation after fixing inputs, but inspect registry + state before retrying an ambiguous publication/transport or any reported + post-publication Cosign failure; +- when a batch fails after a sibling target was published, inventory every + reported canonical reference and apply explicit retain/delete policy to the + subject, navigation tag, attachments, and tagged/untagged package versions. + +Registry cleanup is an external destructive operation. Require a reviewed list +of exact subject, attachment-tag, and package-version targets plus retention +approval; do not delete by a broad prefix or because a successful bundle was +absent. + +## Unsigned Bundle Limitation + +The package manifest and portable tarball are not themselves signed in +`v0.9.0`. Their evidence file hashes detect accidental or isolated tampering, +and an independently protected archive checksum binds the complete bytes. An +actor able to replace both the artifact and its external protected record can +coordinate a replacement that these checks cannot detect. Use storage/control +planes with independent identities, versioned history, approvals, and audit +logs. Signed portable bundles and Deploy-time registry Cosign verification are +future contracts, not behavior implied by this release. + +## Failure Meaning + +- Artifact ID/run not found: retention expired, the wrong protected record was + selected, or the deploy identity lacks artifact read access. +- Archive checksum mismatch: downloaded bytes do not match the approved record; + quarantine them without extraction. +- `tarfile` filter error: a member path/link/type is unsafe; treat the archive as + untrusted. +- Source mismatch or planned status: the archive is not the approved live + package for this release SHA. +- Evidence hash mismatch: package bytes changed after successful Package. +- Pull failure inside a real platform script: the platform pull identity or + registry retention is wrong; Package credentials are intentionally absent. + +## Checkpoint + +Record and verify this final chain: + +```text +protected artifact ID + producing run + -> downloaded archive SHA-256 + -> safely restored complete package directory + -> manifest source_revision == independently recorded full SHA + -> manifest/evidence integrity preflight + -> unchanged repository@sha256 reference + -> platform deployment result +``` + +The rollback checkpoint is identical except that every protected input and the +deployed digest come from the selected earlier record. + +Next: return to the [OCI tutorial index](..) or use the +[OCI application-image reference](../../../oci-application-images) while +adapting this path to a real service. diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-release-workflow.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-release-workflow.md new file mode 100644 index 0000000..f9542f2 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-release-workflow.md @@ -0,0 +1,158 @@ +--- +title: "Package Release Workflow" +sidebar_label: "Package Release Workflow" +--- + +Package release can run either as part of the main trusted `workflow` or as a +dedicated trusted workflow. Compose it into `workflow` when the same CI job +should deploy applications and release npm packages; keep it standalone for +package-only repositories, release debugging, or stricter operational +separation. + +## Composed Workflow + +Select package release explicitly: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + dry-run: "false" + release-targets-json: '["npm"]' + deploy-env: | + GCP_PROJECT_ID=${{ vars.GCP_PROJECT_ID }} + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +In this mode, Rush Delivery shares source acquisition, metadata validation, +Rush install cache, and the Rush lifecycle. When `npm` is selected, it runs the +all-project lifecycle once, then starts deploy and npm package release side +effects after the shared prerequisites pass. Deploy tags continue to point at +the original source SHA. The Rush package release branch pushes its generated +version commit to `versioning.target_branch`. + +Deploy and package release side effects are concurrent but not transactional. +Rush Delivery waits for all started branches and reports every failure, but a +successful external side effect may already exist if another branch fails. + +## Standalone Workflow + +Run package release as a dedicated trusted workflow when it has a different +permission profile from PR validation and deploy release workflows. + +LabKit uses `.github/workflows/package-release.yaml` with +`entrypoint: release-packages`: + +```yaml +name: package-release + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: package-release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release-packages: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Run Rush Delivery package release + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: release-packages + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +`contents: write` is required because Rush publishes a generated version commit +back to `versioning.target_branch`. The action appends `GITHUB_TOKEN` to the +generated release env file by default, so the release entrypoint can use the +same token for Git source acquisition and the final push. + +`packages: write` is not required for npmjs publishing by itself. Add package +registry permissions only when Rush Delivery provider adapters use GHCR-backed +toolchain images or Rush install cache. + +## What The Entrypoint Does + +The live release path is: + +1. Acquire source through Git source mode. +2. Validate Rush and `.dagger/release/npm.yaml` metadata. +3. Restore or prepare Rush install state. +4. Run Rush `build`, `lint`, `test`, and `verify`. +5. Configure npm token auth from release env. +6. Configure process-only Git push auth from source auth. The token stays in a + Dagger secret environment and a static askpass helper reads it only when Git + prompts; no token or derived Basic header is written to `.git/config`. +7. Prepare the local target branch. +8. Run `rush publish --apply --target-branch --publish`. + +The final Rush step applies change files, updates package versions and +changelogs, publishes packages, commits the version changes, and pushes that +commit back to the target branch. + +## PR Validation + +When `.dagger/release/npm.yaml` exists, Rush Delivery PR validation includes +release-readiness verification: + +```yaml +permissions: + contents: read + packages: read + +steps: + - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + toolchain-image-provider: github + rush-cache-provider: github +``` + +The validation entrypoint uses read-only provider policies by default. It can +reuse existing provider artifacts, but it does not publish new images or Rush +cache from PRs. Package release credentials are not passed to PR validation. + +## Local Dry-Run + +Use local-copy source mode to test metadata and release behavior before pushing: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. -- release-packages \ + --git-sha="$(git rev-parse HEAD)" \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off +``` + +The dry-run path reads release metadata and runs the release lifecycle, but it +does not require `NPM_TOKEN`, does not publish packages, and does not push a +version commit. + +## Checklist + +- Package release workflow runs only from trusted events. +- Live release job has `contents: write`. +- `NPM_TOKEN` is stored as a secret. +- `release-env` contains npm credentials. +- Provider settings match the repository metadata. +- PR validation verifies Rush change files before merge. +- Local dry-run succeeds before the first live release. + +From here, use [Metadata](../../metadata), [GitHub Action Usage](../../github-action), +and [Entrypoints](../../entrypoints) when you need exact field and API details. diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-targets.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-targets.md new file mode 100644 index 0000000..6788def --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/package-targets.md @@ -0,0 +1,157 @@ +--- +title: "Package Targets" +sidebar_label: "Package Targets" +--- + +Package targets describe the deploy artifact for each deploy target. Rush +Delivery builds selected Rush projects first, then materializes the artifacts +declared in `.dagger/package/targets`. + +The example has two filesystem package styles. v0.9.0 also supports an opt-in +OCI application image. + +## Rush Deploy Archive + +The backend target uses a Rush deploy archive: + +```yaml +name: server + +artifact: + kind: rush_deploy_archive + project: server + scenario: server + output: common/deploy/server +``` + +This points at: + +- Rush project `server` +- Rush deploy scenario `server` +- output directory `common/deploy/server` + +The deploy scenario lives in +[`common/config/rush/deploy-server.json`](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk/blob/main/common/config/rush/deploy-server.json). +It tells Rush deploy how to gather the server package and its production +dependencies into a deployable directory. + +Use this style when a backend service needs its package, transitive runtime +dependencies, and local workspace dependencies collected into one bundle. + +## Directory Artifact + +The frontend target uses a directory artifact: + +```yaml +name: webapp + +artifact: + kind: directory + path: apps/webapp/dist +``` + +Use this style when the build already produces a deployable directory. Static +frontend assets are the common case. + +## OCI Image Artifact + +Use an OCI artifact when the deploy platform consumes a container image: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +name: server + +artifact: + kind: oci_image + context: . + dockerfile: deploy/images/server.Dockerfile + image: server + platform: linux/amd64 + scan: + fail_on: [high, critical] +``` + +The normal workflow runs the Rush build first. Package then builds from that +prepared workspace, scans before publishing, publishes once, signs and attests +the returned digest, and writes the verified reference to the v2 manifest. +Keep application-specific build logic in the Rush project and Dockerfile. + +OCI targets need a full Git SHA and, for live runs, a named provider from +`.dagger/application-images/providers.yaml`. Directory/archive targets keep +their existing unversioned manifest and need no provider metadata after an +upgrade. + +Use the dedicated +[OCI application images tutorial](../oci-application-images) for a +runnable target, then consult the [production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting). + +## Build Environment + +Package metadata can allow build-time environment variables for the generic Rush +`verify`, `lint`, `test`, and `build` stage. + +Use `pass_env` when the variable name should stay the same inside the build +container. Use `map_env` when CI stores the value under one name, but the build +tool expects another: + +```yaml +name: webapp + +build: + pass_env: + - WEBAPP_URL + map_env: + VITE_GRAPHQL_HTTP: WEBAPP_VITE_GRAPHQL_HTTP + dry_run_defaults: + WEBAPP_URL: https://webapp.example.test + WEBAPP_VITE_GRAPHQL_HTTP: https://api.example.test/graphql + +artifact: + kind: directory + path: apps/webapp/dist +``` + +The source values come from the same deploy env file that the action prepares +from `deploy-env`. Rush Delivery applies only the variables allowed by selected +package targets. + +The Rush build stage is shared for selected targets. If two selected package +targets resolve the same target variable to different values, Rush Delivery +fails instead of choosing one silently. + +There is no precedence between `pass_env` and `map_env`. Both add variables to +the build container. If they produce the same output name with different values, +the run fails so build configuration cannot be changed by a silent override. + +## Artifact Paths In Deploy Scripts + +Rush Delivery passes the selected artifact path to deploy scripts through +`ARTIFACT_PATH`. + +For the backend, the deploy script expects an extracted Rush deploy bundle with +`apps/server` inside it. For the frontend, the deploy script expects a static +assets directory. + +Keep deploy scripts defensive. They should fail early if `ARTIFACT_PATH` does +not point at the expected shape. + +OCI targets do not set `ARTIFACT_PATH`. They set +`ARTIFACT_IMAGE_REFERENCE` to the immutable `repository@sha256:...` value and +mount verified evidence at `ARTIFACT_EVIDENCE_DIR`. Deploy the reference as-is; +do not rebuild or replace it with the navigation tag. + +## Checklist + +- Create one package target for every deploy target. +- Use `rush_deploy_archive` for backend/runtime bundles. +- Use `directory` for already-built static assets. +- Use `oci_image` for one explicitly targeted, scanned, signed application + image that the deployment platform consumes by digest. +- Allow build-time env with `build.pass_env` and `build.map_env` only when the + Rush build really needs it. +- Keep artifact paths relative to the repository root. +- Make deploy scripts validate the artifact shape before deploying. + +Next: [Deploy Mesh](../deploy-mesh). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/provider-artifacts.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/provider-artifacts.md new file mode 100644 index 0000000..5bc1d8e --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/provider-artifacts.md @@ -0,0 +1,141 @@ +--- +title: "Provider Artifacts" +sidebar_label: "Provider Artifacts" +--- + +Provider artifacts are the reusable pieces that make Rush Delivery fast in CI: + +- toolchain images +- Rush install cache + +Application images use a third, deliberately separate provider contract. They +are product deploy artifacts rather than reusable framework acceleration +artifacts, so they do not share these image names, policies, or retention. + +The example stores both in GitHub Container Registry through metadata in: + +- [`.dagger/toolchain-images/providers.yaml`](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk/blob/main/.dagger/toolchain-images/providers.yaml) +- [`.dagger/rush-cache/providers.yaml`](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk/blob/main/.dagger/rush-cache/providers.yaml) + +## Toolchain Images + +Toolchain images are built from runtime metadata. For example, the Rush workflow +toolchain contains Node and Git, while deploy target toolchains can include +cloud CLIs, Docker CLI, or other deploy tools. + +The metadata points Rush Delivery at GHCR: + +```yaml +providers: + github: + kind: github_container_registry + registry: ghcr.io + image_namespace: rush-delivery-toolchains + repository_env: GITHUB_REPOSITORY + token_env: GITHUB_TOKEN + username_env: GITHUB_ACTOR +``` + +Rush Delivery derives content-addressed tags from the normalized toolchain spec. +Changing install commands, base image, or runtime identity creates a different +tag. + +## Rush Install Cache + +The Rush cache stores selected install directories in a compressed OCI image. +The cache identity is a stable project snapshot, controlled by `cache.version`: + +```yaml +cache: + version: v1 + paths: + - common/temp/install-run + - common/temp/node_modules + - common/temp/pnpm-store +``` + +Rush Delivery restores the `v1` snapshot when it exists, runs `rush install`, +and lets Rush reconcile lockfile or package-manager changes. If you want to +discard the old snapshot intentionally, bump `version` to a new OCI tag such as +`v2`. + +## Policies + +Use different policies for pull requests and trusted release workflows. + +For pull requests: + +```yaml +permissions: + contents: read + packages: read + +with: + entrypoint: validate + toolchain-image-provider: github + toolchain-image-policy: pull-or-build + rush-cache-provider: github + rush-cache-policy: pull-or-build +``` + +`pull-or-build` pulls existing artifacts. On miss, it builds or installs +locally and does not publish. This keeps PRs read-only. + +For trusted release workflows: + +```yaml +permissions: + contents: write + packages: write + +with: + toolchain-image-provider: github + rush-cache-provider: github +``` + +`lazy` is the trusted workflow policy. Toolchain images are published when they +are missing. Rush cache is restored when available, then the post-install cache +is published after a successful install. + +## Application Image Provider + +If a later package target uses `artifact.kind: oci_image`, add +`.dagger/application-images/providers.yaml`. This is illustrative provider +metadata; replace the example registry and namespace with an accepted registry +recipe: + +```yaml +providers: + release: + kind: oci_registry + registry: registry.example.com + repository_prefix: product/images + username_env: OCI_USERNAME + token_env: OCI_TOKEN + signing_key_env: OCI_SIGNING_KEY + signing_password_env: OCI_SIGNING_PASSWORD + verification_key_env: OCI_SIGNING_PUBLIC_KEY +``` + +Select it only for a trusted live workflow. Provider `off` is sufficient for +filesystem-only projects and OCI dry runs. Registry/signing credentials are +Package-only Dagger secrets; deployment receives the verified digest instead. +Do not put these values in `runtime-file-map` or expose their names from package +build or deploy target metadata. + +Continue OCI setup in the dedicated +[OCI application images tutorial](../oci-application-images). Before a +live release, review the [production guide](../../oci-application-images), +[registry recipes](../../oci-registry-recipes), and +[troubleshooting guide](../../oci-application-image-troubleshooting). + +## Checklist + +- Configure provider metadata in `.dagger`. +- Pass `GITHUB_ACTOR`, `GITHUB_REPOSITORY`, and `GITHUB_TOKEN` through CI env. +- Use `packages: read` for PR validation. +- Use `packages: write` only in trusted workflows. +- Bump `cache.version` only when you intentionally want a fresh Rush install + cache snapshot. + +Next: [Package Targets](../package-targets). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/release-metadata.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/release-metadata.md new file mode 100644 index 0000000..3efe5d6 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/release-metadata.md @@ -0,0 +1,101 @@ +--- +title: "Release Metadata" +sidebar_label: "Release Metadata" +--- + +Package release metadata lives in `.dagger/release/npm.yaml`. It is intentionally +small because Rush and npm already own most package release policy. + +LabKit uses this shape: + +```yaml +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/npm-release.schema.json + +kind: npm + +versioning: + strategy: rush-change-files + target_branch: main + +auth: + kind: token + token_env: NPM_TOKEN + +publish: + registry: https://registry.npmjs.org/ + tag: latest + access: public + provenance: false +``` + +## Versioning + +`versioning.strategy` is currently `rush-change-files`. Rush Delivery uses this +metadata in two places: + +- PR validation runs `rush change --verify --target-branch `. +- Live package release runs `rush publish --apply --target-branch + --publish`. + +For live Git source releases, Rush Delivery fetches and prepares +`target_branch` locally before invoking `rush publish`. Rush can then check out +that branch and push its generated version commit. + +## Auth + +`auth.kind: token` means Rush Delivery reads the token from the release env +file and exposes it to the release runtime under `auth.token_env`. + +The project wires that env into npm through +`common/config/rush/.npmrc-publish`: + +```text +//registry.npmjs.org/:_authToken=${NPM_TOKEN} +``` + +Use `release-env` or `release-env-file` for package publishing credentials. +Do not put npm credentials in `deploy-env`; deploy env belongs to build/deploy +targets and has a different trust boundary. + +## Publish Options + +The `publish` block passes npm-specific policy to Rush publish: + +- `registry`: npm registry URL. +- `tag`: npm dist-tag, defaulting to `latest`. +- `access`: `public` or `restricted`. +- `provenance`: npm provenance toggle, defaulting to `false`. + +Keep `provenance` omitted or set to `false` for the default Dagger-contained +token flow. Enable it only after the release runtime is wired so npm can detect +a supported provenance provider from inside the publishing environment. + +## Provider Metadata + +Package-only repositories do not need deploy metadata. They also do not need +Rush Delivery provider metadata unless they opt into provider-backed +toolchain images or Rush install cache. + +LabKit keeps providers off in the release workflow: + +```yaml +with: + toolchain-image-provider: off + rush-cache-provider: off +``` + +Use provider `github` only when the repository has matching +`.dagger/toolchain-images` or `.dagger/rush-cache` metadata and the workflow +has package registry permissions. + +## Checklist + +- Add `.dagger/release/npm.yaml`. +- Point the schema comment at the exact Rush Delivery version. +- Keep `target_branch` aligned with the branch Rush should update. +- Add `common/config/rush/.npmrc-publish`. +- Store `NPM_TOKEN` as a CI secret. +- Keep npm credentials in release env, not deploy env. +- Leave provenance disabled unless the runtime supports it. + +Next: [Package Release Workflow](../package-release-workflow). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-commands.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-commands.md new file mode 100644 index 0000000..4c19eaa --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-commands.md @@ -0,0 +1,69 @@ +--- +title: "Rush Commands" +sidebar_label: "Rush Commands" +--- + +Rush Delivery uses Rush commands to validate and build selected projects. The +example repository defines repo-level commands in +[`common/config/rush/command-line.json`](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk/blob/main/common/config/rush/command-line.json). + +The important commands are: + +- `verify` +- `lint` +- `test` +- `build` + +`build` is Rush's normal build command. The others are custom bulk commands. +Rush Delivery can run them against affected projects instead of blindly running +the whole repository. + +## Validation Commands + +The example configures `verify`, `lint`, and `test` as bulk commands. Each +command runs an npm script from each selected project folder when the project +defines that script. + +The useful pattern is: + +```json +{ + "commandKind": "bulk", + "name": "lint", + "shellCommand": "npm run lint --if-present", + "enableParallelism": true, + "ignoreMissingScript": true +} +``` + +This lets library, server, and webapp projects participate differently while +sharing one CI path. + +## Build Command + +Rush Delivery uses Rush build selection to build the projects needed for deploy +targets. A deploy target does not have to map one-to-one to a single app, but +the example keeps the mapping simple: + +- deploy target `server` builds project `server` +- deploy target `webapp` builds project `webapp` + +## Contract Drift Checks + +The example server has a `verify` script that regenerates a GraphQL schema and +fails if the committed API contract changed unexpectedly. That is not required +by Rush Delivery, but it shows the kind of project-specific check that belongs +behind a Rush command. + +The transferable rule is simple: put project checks in project scripts, expose +shared command names through Rush, and let Rush Delivery call Rush. + +## Checklist + +- Projects that need CI validation expose `verify`, `lint`, or `test` scripts. +- Projects that produce deploy artifacts expose `build`. +- Custom Rush commands use `ignoreMissingScript` when not every project has the + script. +- Commands are safe to run from a clean CI checkout. + +Next: [Dagger Metadata Map](../dagger-metadata-map). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-monorepo-baseline.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-monorepo-baseline.md new file mode 100644 index 0000000..2b80906 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/rush-monorepo-baseline.md @@ -0,0 +1,75 @@ +--- +title: "Rush Monorepo Baseline" +sidebar_label: "Rush Monorepo Baseline" +--- + +Rush Delivery starts from Rush. Before adding `.dagger` metadata, make sure the +monorepo has stable Rush project names, project folders, package scripts, and a +committed lockfile. + +The example repository declares its projects in +[`rush.json`](https://github.com/BootstrapLaboratory/typescript_monorepo_nestjs_relay_trunk/blob/main/rush.json). + +```json +{ + "projects": [ + { "packageName": "api-contract", "projectFolder": "libs/api" }, + { "packageName": "webapp", "projectFolder": "apps/webapp" }, + { "packageName": "server", "projectFolder": "apps/server" } + ] +} +``` + +Those package names become the vocabulary used by Rush Delivery. A deploy target +can be named `server`, a package target can build the Rush project `server`, and +validation can run Rush commands against affected projects. + +## Project Names Matter + +Keep Rush project names short, stable, and meaningful. They appear in: + +- `rush.json` +- package metadata such as `.dagger/package/targets/server.yaml` +- deploy metadata such as `.dagger/deploy/targets/server.yaml` +- validation metadata such as `.dagger/validate/targets/server.yaml` +- Rush affected-project output + +The names do not have to match folder names, but doing so makes the metadata +much easier to scan. In the example, `server` lives in `apps/server`, and +`webapp` lives in `apps/webapp`. + +## Lockfile And Package Manager + +Rush Delivery expects normal Rush install behavior. In the example, Rush uses +PNPM and tracks the install state with: + +- `common/config/rush/pnpm-lock.yaml` +- `common/config/rush/pnpm-config.json` +- `common/config/rush/version-policies.json` + +Rush Delivery restores the configured install cache snapshot first, then Rush +reconciles the actual dependency state during `rush install`. + +## Root Scripts Are Optional + +The example root `package.json` offers convenience scripts such as: + +```sh +npm run rush:install +npm run rush:build +npm run webapp:build:pages +``` + +Rush Delivery does not require those exact scripts. What matters is that the +repo can be installed and built through Rush from the repository root. + +## Checklist + +- `rush.json` exists and lists every project. +- Each project has a `package.json`. +- Rush can install dependencies from the repo root. +- Buildable projects have a `build` script. +- The lockfile is committed. +- Project names are stable enough to reference from `.dagger` metadata. + +Next: [Rush Commands](../rush-commands). diff --git a/docs-versions/versioned_docs/version-v0.9.0/tutorial/validation-targets.md b/docs-versions/versioned_docs/version-v0.9.0/tutorial/validation-targets.md new file mode 100644 index 0000000..dde80f1 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/tutorial/validation-targets.md @@ -0,0 +1,80 @@ +--- +title: "Validation Targets" +sidebar_label: "Validation Targets" +--- + +Rush Delivery always runs Rush validation for affected projects. Validation +targets add product-specific runtime checks when a selected project needs more +than `verify`, `lint`, `test`, and `build`. + +The example has a backend validation target in +`.dagger/validate/targets/server.yaml`. + +## Services + +The backend validation target starts Postgres and Redis: + +```yaml +services: + postgres: + image: postgres:16-alpine + ports: + - 5432 + + redis: + image: redis:7-alpine + ports: + - 6379 +``` + +Services are available by name from validation steps. The migration step can use +`postgres` as `DATABASE_HOST`, and the server can use `redis` in `REDIS_URL`. + +## Steps + +The example validates the backend in three phases: + +1. run database migrations +2. start the production server +3. run a smoke check against the server + +The long-running server step is declared as a service step, while migrations +and smoke checks are command steps. + +## What Belongs In Validation Metadata + +Put checks here when they need runtime dependencies or multi-step orchestration. +Good candidates: + +- migration checks +- service startup checks +- API smoke tests +- contract checks that need a real service +- broker, database, or cache integration checks + +Keep fast project-local checks in Rush commands. A TypeScript compile, unit +test, or linter usually belongs in the project scripts called by Rush. + +## PR Behavior + +In PRs, validation should be read-only against provider artifacts: + +```yaml +with: + entrypoint: validate + toolchain-image-provider: github + rush-cache-provider: github +``` + +This still reuses toolchain images and Rush cache when they exist, but it does +not publish from a PR. + +## Checklist + +- Add validation target metadata only for checks that need orchestration. +- Keep service names stable and use them in step env. +- Prefer production-like commands where practical. +- Make smoke checks deterministic and bounded by timeouts. +- Keep PR provider policies read-only. + +Next: [GitHub Actions](../github-actions). diff --git a/docs-versions/versioned_docs/version-v0.9.0/upgrade-v0-9-0.md b/docs-versions/versioned_docs/version-v0.9.0/upgrade-v0-9-0.md new file mode 100644 index 0000000..15f47de --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/upgrade-v0-9-0.md @@ -0,0 +1,122 @@ +--- +id: "upgrade-v0-9-0" +title: "Upgrade To v0.9.0" +sidebar_label: "Upgrade To v0.9.0" +description: "Upgrade and recover safely from v0.8.1." +--- + +Rush Delivery `v0.9.0` adds environment-selected public OCI coordinates, +bounded local-copy imports, and deterministic project-owned Rush tools. It does +not change the package-manifest v2 handoff, provider-off defaults, static OCI +provider behavior, or the unconfigured Node-only toolchain. + +## Compatibility Summary + +| Existing project | Required change | +| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | +| Git source mode, any existing artifact mix | Update the Action/module tag only | +| Static `oci_registry` provider | Update the tag only; `registry` and `repository_prefix` remain valid | +| No `.dagger/toolchains/rush.yaml` | None; toolchain v1 spec/hash/cache behavior is preserved | +| Filesystem-only or provider-off | None; OCI provider metadata remains optional/unused | +| Direct top-level local `dagger call` | None; released static filters are preserved | +| Action `local_copy` or new local launcher | Review the seven bounded defaults; include an intentionally required matching path or temporarily choose `legacy` | + +The only default behavior change is at the new caller-side boundary for Action +`local_copy`: `source-import-policy` defaults to `bounded`. This prevents large +dependency/cache trees from being uploaded. A project that intentionally uses a +matching path must declare a later inclusion in +`.dagger/source-import.ignore`. + +## Pre-Upgrade Inventory + +Record the current tag, source mode, provider selection, and one successful dry +run. For local-copy callers, locate required paths matching: + +```text +**/node_modules +**/.venv +**/__pycache__ +**/.rush +**/rush-logs +.trunk/out +.trunk/logs +``` + +Do not add blanket inclusions. For each required path, identify the entrypoint +that consumes it and add the narrowest `!` rule. Split-stage Package callers +must account for built outputs, `.dagger/runtime`, package manifests, and OCI +evidence explicitly. + +## Upgrade References + +Update Action and module references: + +```yaml +uses: BootstrapLaboratory/rush-delivery@v0.9.0 +``` + +```sh +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +``` + +Use exact v0.9.0 editor schemas: + +```text +https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/.schema.json +``` + +If local worktrees are used, install the checksummed launcher exactly as shown +in the [bounded local-copy guide](../local-copy-source-imports). + +## Canary Sequence + +1. Run `validate-metadata-contract` against the upgraded module. +2. Run provider-off dry runs for the same forced targets used in the v0.8.1 + baseline. +3. If using local copy, run bounded mode and confirm Git history, affected + targets, and every required re-included output. +4. If adopting environment coordinates, select a non-production profile in a + named-provider dry run. Confirm the planned repository and that no credential + value is needed. +5. If adopting a project toolchain, run provider off first and assert the tool + version in Rush lifecycle scripts. Populate provider cache only from a + trusted job. +6. Run one live non-production OCI publication. Verify signature, + attestations, evidence, manifest repository/digest, and digest-only Deploy. +7. Promote the unchanged metadata and version pin to production. + +## Optional Feature Adoption + +Environment coordinates use exactly one field from each pair: + +- `registry` or `registry_env`; and +- `repository_prefix` or `repository_prefix_env`. + +They can be adopted independently, so static/environment mixed definitions are +valid. Coordinate values are public routing data. Credential environment names +and values retain the Package-only protections. Follow the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles). + +Project tools are opt-in through `.dagger/toolchains/rush.yaml`. Absence is the +compatibility path. Follow the [toolchain production guide](../rush-toolchain) +and [mixed-language tutorial](../tutorial/mixed-node-python-toolchain). + +## Recovery + +If bounded local copy omits a required path, choose `legacy` for the immediate +retry, then add and test a narrow inclusion before restoring `bounded`. Git +source mode is unaffected and never reads the ignore file. + +If a configured toolchain fails, remove the new metadata to return to the exact +default toolchain, or revert the metadata to the last reviewed digest/checksum. +Do not bypass checksum or base-image pinning. + +If dynamic coordinates select the wrong repository, stop before live Package, +correct the public deployment env value, and repeat the named dry run. Deploy +does not reload coordinates: a package already published and handed off by +digest continues to deploy that immutable packaged result. + +Rolling the Action/module reference back to `v0.8.1` is valid only for metadata +that does not use the new coordinate or toolchain fields. Retain the v0.9.0 +package manifest/evidence with any v0.9.0 publication; do not reconstruct a +repository during rollback. diff --git a/docs-versions/versioned_docs/version-v0.9.0/workflows.md b/docs-versions/versioned_docs/version-v0.9.0/workflows.md new file mode 100644 index 0000000..b4e7899 --- /dev/null +++ b/docs-versions/versioned_docs/version-v0.9.0/workflows.md @@ -0,0 +1,223 @@ +--- +id: "workflows" +title: "Workflow Guide" +sidebar_label: "Workflow Guide" +--- + +## Local Framework Check + +Use `self-check` before changing metadata, schemas, or Dagger source: + +```sh +dagger call self-check +``` + +## Local Provider-Off Dry Run + +This exercises the full release composition without GHCR, cloud credentials, or +a Docker socket against local unpushed changes: + +```sh +./rush-delivery-local \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --repo=. \ + -- \ + workflow \ + --git-sha="$(git rev-parse HEAD)" \ + --event-name=workflow_call \ + --force-targets-json='["server","webapp"]' \ + --dry-run=true \ + --toolchain-image-provider=off \ + --rush-cache-provider=off \ + --application-image-provider=off +``` + +Dry-runs use package and deploy target `dry_run_defaults` for allowed build and +runtime environment values. +The checksummed launcher applies bounded source exclusions before transfer; see +[bounded local-copy imports](../local-copy-source-imports). + +## CI Release Workflow + +A CI provider should keep provider-specific setup small, then call the Dagger +workflow. + +For GitHub Actions, prefer the repository action wrapper: + +```yaml +- name: Rush Delivery + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + force-targets-json: ${{ inputs.force_targets_json || '[]' }} + environment: prod + dry-run: "false" + release-targets-json: '["npm"]' + runtime-file-map: | + ${{ steps.auth.outputs.credentials_file_path }}=>gcp-credentials.json + deploy-env: | + GCP_PROJECT_ID=${{ vars.GCP_PROJECT_ID }} + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +See [GitHub Action usage](../github-action) for the complete production shape. + +For pull-request validation, use the same action with the `validate` +entrypoint. The action defaults provider policies to `pull-or-build` for +validation. If npm release metadata is configured, validation also verifies Rush +change files: + +```yaml +- name: Rush Delivery validation + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: validate + toolchain-image-provider: github + rush-cache-provider: github +``` + +For npm package release, prefer composing it into `workflow` with explicit +`release-targets-json: '["npm"]'` when the same trusted CI job should deploy +applications and release packages. Rush Delivery shares source acquisition, +metadata validation, Rush install cache, and the build lifecycle, then starts +deploy and package release side effects after the shared prerequisites pass. + +The standalone `release-packages` entrypoint remains useful for package-only +projects and release debugging. A package-only project can keep provider +adapters off: + +```yaml +- name: Rush Delivery package release + uses: BootstrapLaboratory/rush-delivery@v0.9.0 + with: + entrypoint: release-packages + dry-run: "false" + toolchain-image-provider: off + rush-cache-provider: off + release-env: | + NPM_TOKEN=${{ secrets.NPM_TOKEN }} +``` + +The action appends `GITHUB_TOKEN` to the release env file by default, so live +package release can push the Rush-generated version commit back to the target +branch. The workflow job needs `contents: write`; it needs `packages: read` or +`packages: write` only when using GHCR-backed Rush cache or toolchain images. +Rush Delivery also prepares the release target branch locally before `rush +publish`, because Rush checks out that branch for the final version-commit +merge. + +Composed package release does not change deploy tag identity. Deploy tags +continue to point at the original source SHA. Rush package release creates and +pushes its own version commit to the metadata `target_branch`. + +Deploy and package release side effects are concurrent but not transactional. +Rush Delivery waits for both started branches and reports every failure, but a +successful external side effect may already exist if the other branch fails. + +NPM provenance defaults to `false` in `.dagger/release/npm.yaml`. Opt in only +when npm can detect a supported provenance provider from inside the Dagger +release runtime. + +For a raw Dagger command this means: + +- Install the Dagger CLI. +- Authenticate to external providers when live deploy targets need it. +- Write a deploy environment file with build-time values and deploy-platform + configuration or credentials. +- Write a workflow environment file for shared source/provider values. +- Write a release environment file when package release needs registry + credentials. +- Copy deploy-platform credential files into a runtime files directory when + targets mount files. Do not put OCI registry tokens or Cosign material in + that directory. +- Call `dagger -m "$RUSH_DELIVERY_MODULE" call workflow`. + +The CI provider should pass source coordinates rather than doing release logic +itself. Dagger owns source acquisition, deploy tag fetching, detection, build, +package, deployment, and deploy tag updates. + +## Recommended CI Shape + +```sh +mkdir -p "$RUNNER_TEMP/rush-delivery-runtime-files" +cp "$GCP_CREDENTIALS_FILE" \ + "$RUNNER_TEMP/rush-delivery-runtime-files/gcp-credentials.json" + +dagger -m "$RUSH_DELIVERY_MODULE" call workflow \ + --git-sha="$GITHUB_SHA" \ + --event-name="$GITHUB_EVENT_NAME" \ + --force-targets-json="$FORCE_TARGETS_JSON" \ + --pr-base-sha="$PR_BASE_SHA" \ + --deploy-tag-prefix="$DEPLOY_TAG_PREFIX" \ + --artifact-prefix="$DEPLOY_ARTIFACT_PREFIX" \ + --environment=prod \ + --dry-run=false \ + --workflow-env-file="$WORKFLOW_ENV_FILE" \ + --deploy-env-file="$DEPLOY_ENV_FILE" \ + --release-targets-json="$RELEASE_TARGETS_JSON" \ + --release-env-file="$RELEASE_ENV_FILE" \ + --host-workspace-dir="$GITHUB_WORKSPACE" \ + --toolchain-image-provider="$TOOLCHAIN_IMAGE_PROVIDER" \ + --toolchain-image-policy="$TOOLCHAIN_IMAGE_POLICY" \ + --rush-cache-provider="$RUSH_CACHE_PROVIDER" \ + --rush-cache-policy="$RUSH_CACHE_POLICY" \ + --application-image-provider=off \ + --source-mode=git \ + --source-repository-url="$SOURCE_REPOSITORY_URL" \ + --source-ref="$SOURCE_REF" \ + --source-auth-token-env=GITHUB_TOKEN \ + --runtime-files="$RUNNER_TEMP/rush-delivery-runtime-files" +``` + +First-class OCI targets do not need a Docker socket. Add `--docker-socket` only +when an existing project-owned deploy script still invokes Docker directly. + +This is a filesystem-first baseline. It neither selects an application-image +provider nor needs OCI registry or Cosign credentials. + +## OCI Package And Deploy Boundary + +With a named application-image provider, each selected `oci_image` target is +built from the prepared workspace, scanned before publication, published once, +signed and attested, and written to the package manifest only after +verification. Deploy scripts receive the immutable digest reference and the +target-scoped evidence directory; registry and signing credentials do not cross +the Package boundary. When no selected package target is OCI, the provider +input, provider metadata file, and provider credentials are ignored. + +Provider `off` remains the default and needs no metadata for filesystem-only +projects. OCI dry runs are also valid with provider `off`: they report relative +image/platform intent without resolving credentials or producing a fake digest. +Named providers may choose registry authority and repository prefix from public +workflow/deploy environment values. Package resolves the coordinates once; +Deploy still consumes only the manifest digest. Follow the +[environment-profile tutorial](../tutorial/oci-application-images/environment-profiles). + +Publication is not transactional. A signing or verification failure after +publish can leave an orphaned registry digest or navigation tag, but Rush +Delivery stops before writing a successful manifest or starting Deploy. Begin +with the +[OCI application images tutorial](../tutorial/oci-application-images), +then consult the [production guide](../oci-application-images), +[registry recipes](../oci-registry-recipes), and +[troubleshooting guide](../oci-application-image-troubleshooting) for live +release, retention, rollback, and incident handling. + +## Project-Owned Rush Tools + +Optional `.dagger/toolchains/rush.yaml` extends the common Rush container before +Rush install, detection, build, validation, Rush-requiring Package, and package +Release. The metadata is part of the content-addressed toolchain provider key. +Absence preserves the v0.8.1 Node-only graph. See the +[toolchain guide](../rush-toolchain) for the security/update contract and the +[mixed Node/Python tutorial](../tutorial/mixed-node-python-toolchain) for a +complete provider-off and cached run. + +## Split Stage Workflows + +The stage-level APIs exist for CI systems that need separate jobs. Prefer the +single `workflow` entrypoint unless there is a provider-specific reason to split +handoff between detect, build, package, and deploy. + +When splitting stages, persist the CI plan and package manifest as files rather +than re-encoding stage state in CI-specific outputs. diff --git a/docs-versions/versioned_sidebars/version-v0.9.0-sidebars.json b/docs-versions/versioned_sidebars/version-v0.9.0-sidebars.json new file mode 100644 index 0000000..74001ae --- /dev/null +++ b/docs-versions/versioned_sidebars/version-v0.9.0-sidebars.json @@ -0,0 +1,224 @@ +{ + "docsSidebar": [ + { + "type": "doc", + "id": "introduction", + "label": "Introduction" + }, + { + "type": "doc", + "id": "github-action", + "label": "GitHub Action" + }, + { + "type": "doc", + "id": "api", + "label": "Public API" + }, + { + "type": "doc", + "id": "entrypoints", + "label": "Entrypoints" + }, + { + "type": "doc", + "id": "workflows", + "label": "Workflow Guide" + }, + { + "type": "doc", + "id": "metadata", + "label": "Metadata" + }, + { + "type": "doc", + "id": "providers", + "label": "Providers" + }, + { + "type": "doc", + "id": "local-copy-source-imports", + "label": "Bounded Local-Copy Imports" + }, + { + "type": "doc", + "id": "rush-toolchain", + "label": "Project-Owned Rush Toolchain" + }, + { + "type": "doc", + "id": "upgrade-v0-9-0", + "label": "Upgrade To v0.9.0" + }, + { + "type": "doc", + "id": "oci-application-images", + "label": "OCI Application Images" + }, + { + "type": "doc", + "id": "oci-registry-recipes", + "label": "OCI Registry Recipes" + }, + { + "type": "doc", + "id": "oci-application-image-troubleshooting", + "label": "OCI Application Image Troubleshooting" + }, + { + "type": "doc", + "id": "development", + "label": "Development" + } + ], + "quickStartSidebar": [ + { + "type": "doc", + "id": "quick-start/github-actions", + "label": "GitHub Actions" + }, + { + "type": "doc", + "id": "quick-start/ci-cli", + "label": "CI Using Command Line" + }, + { + "type": "doc", + "id": "quick-start/local-run", + "label": "Local Runs" + } + ], + "tutorialSidebar": [ + { + "type": "doc", + "id": "tutorial", + "label": "Tutorial" + }, + { + "type": "doc", + "id": "tutorial/rush-monorepo-baseline", + "label": "Rush Monorepo Baseline" + }, + { + "type": "doc", + "id": "tutorial/rush-commands", + "label": "Rush Commands" + }, + { + "type": "doc", + "id": "tutorial/dagger-metadata-map", + "label": "Dagger Metadata Map" + }, + { + "type": "doc", + "id": "tutorial/provider-artifacts", + "label": "Provider Artifacts" + }, + { + "type": "doc", + "id": "tutorial/package-targets", + "label": "Package Targets" + }, + { + "type": "doc", + "id": "tutorial/deploy-mesh", + "label": "Deploy Mesh" + }, + { + "type": "doc", + "id": "tutorial/deploy-targets", + "label": "Deploy Targets" + }, + { + "type": "doc", + "id": "tutorial/validation-targets", + "label": "Validation Targets" + }, + { + "type": "doc", + "id": "tutorial/github-actions", + "label": "GitHub Actions" + }, + { + "type": "doc", + "id": "tutorial/local-dry-runs", + "label": "Local Dry Runs" + }, + { + "type": "doc", + "id": "tutorial/adapting-to-your-project", + "label": "Adapt To Your Project" + }, + { + "type": "doc", + "id": "tutorial/npm-package-release-baseline", + "label": "NPM Package Release Baseline" + }, + { + "type": "doc", + "id": "tutorial/release-metadata", + "label": "Release Metadata" + }, + { + "type": "doc", + "id": "tutorial/package-release-workflow", + "label": "Package Release Workflow" + }, + { + "type": "doc", + "id": "tutorial/mixed-node-python-toolchain", + "label": "Mixed Node/Python Toolchain" + }, + { + "type": "category", + "label": "OCI Application Images", + "items": [ + { + "type": "doc", + "id": "tutorial/oci-application-images", + "label": "Overview" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/build-and-scan-target", + "label": "1 - Build And Scan Target" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/provider-off-dry-run", + "label": "2 - Provider-Off Dry Run" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/registry-and-cosign-bootstrap", + "label": "3 - Registry And Cosign Bootstrap" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/publish-and-inspect", + "label": "4 - Publish And Inspect" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/deploy-the-digest", + "label": "5 - Deploy The Digest" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/github-actions", + "label": "6 - GitHub Actions" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/split-stages-and-rollback", + "label": "7 - Split Stages And Rollback" + }, + { + "type": "doc", + "id": "tutorial/oci-application-images/environment-profiles", + "label": "8 - Environment Profiles" + } + ] + } + ] +} diff --git a/docs-versions/versions.json b/docs-versions/versions.json index fd2f6e8..29e3ab5 100644 --- a/docs-versions/versions.json +++ b/docs-versions/versions.json @@ -1,4 +1,5 @@ [ + "v0.9.0", "v0.8.1", "v0.8.0", "v0.7.1", diff --git a/docs/README.md b/docs/README.md index 2e5e7a7..bc609a4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,8 @@ extension surface for validation, packaging, deployment, caches, and toolchains. worktree data before Dagger uploads it, with tested inclusion and recovery. - [Project-owned Rush toolchain](rush-toolchain.md): safely add digest-pinned, checksummed executables to every Rush lifecycle. +- [Upgrade to v0.9.1](upgrade-v0.9.1.md): required patch for bounded local-copy + runs through the GitHub Action. - [Upgrade to v0.9.0](upgrade-v0.9.0.md): compatibility, canary, and recovery guidance for v0.8.1 users. - [OCI application images tutorial](tutorial/oci-application-images/README.md): @@ -51,7 +53,7 @@ contract. These docs explain intent and usage; schemas define file shape. Published schemas are available from the documentation site: - `https://bootstraplaboratory.github.io/rush-delivery/schemas/.schema.json` -- `https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/.schema.json` +- `https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/.schema.json` Use exact versioned schema URLs in project metadata editor hints so older projects keep the schema contract they were written against. The root diff --git a/docs/api.md b/docs/api.md index d394c7a..95196ca 100644 --- a/docs/api.md +++ b/docs/api.md @@ -5,7 +5,7 @@ Rush repository internally. For a checked-out worktree, use the versioned `rush-delivery-local` launcher so exclusions apply before source transfer. ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 ``` GitHub Actions can use the root action wrapper instead of assembling the raw diff --git a/docs/development.md b/docs/development.md index 1072483..2489487 100644 --- a/docs/development.md +++ b/docs/development.md @@ -97,10 +97,10 @@ docs. When adding or renaming public docs pages, update both: Schemas under [`../schemas`](../schemas) are copied into the static site during website builds and are published under `/rush-delivery/schemas/`. Exact release schemas also live under versioned subdirectories such as -`/rush-delivery/schemas/v0.9.0/`. +`/rush-delivery/schemas/v0.9.1/`. When releasing a version that changes schema behavior, create a new versioned -schema snapshot such as `schemas/v0.9.0`, keep earlier directories immutable, +schema snapshot such as `schemas/v0.9.1`, keep earlier directories immutable, and update the root schemas to the current release shape. ## Versioned Docusaurus Docs diff --git a/docs/entrypoints.md b/docs/entrypoints.md index 8ee3555..4a09e0e 100644 --- a/docs/entrypoints.md +++ b/docs/entrypoints.md @@ -4,7 +4,7 @@ When consuming this module from CI, prefer Git source mode so Dagger clones the Rush repository internally: ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 ``` ## `workflow` diff --git a/docs/github-actions.md b/docs/github-actions.md index c5ce6cf..2d7babd 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -28,7 +28,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -52,7 +52,7 @@ steps: with: fetch-depth: 0 - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate repo: . @@ -86,7 +86,7 @@ steps: service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - name: Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: force-targets-json: ${{ inputs.force_targets_json || '[]' }} deploy-tag-prefix: ${{ env.DEPLOY_TAG_PREFIX }} @@ -218,7 +218,7 @@ jobs: permissions: contents: write steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" @@ -300,7 +300,7 @@ The action mode does not replace raw Dagger usage. Local runs, other CI providers, and lower-level debugging can still call the module directly: ```sh -dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 call workflow \ +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 call workflow \ --git-sha="$GITHUB_SHA" \ --source-mode=git \ --source-repository-url="$SOURCE_REPOSITORY_URL" \ diff --git a/docs/local-copy-source-imports.md b/docs/local-copy-source-imports.md index 450fae9..b17b16a 100644 --- a/docs/local-copy-source-imports.md +++ b/docs/local-copy-source-imports.md @@ -1,6 +1,6 @@ # Bounded Local-Copy Source Imports -Rush Delivery `v0.9.0` applies local-copy exclusions before Dagger traverses and +Rush Delivery `v0.9.1` applies local-copy exclusions before Dagger traverses and uploads the repository. Use the bundled `rush-delivery-local` launcher for unpushed worktrees and use Git source mode in CI whenever the source already exists at a remote commit. @@ -15,9 +15,9 @@ POSIX file tools. It does not require Node.js, `jq`, a project install, or GNU ```sh curl --fail --location \ --output rush-delivery-local \ - https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.0/rush-delivery-local + https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.1/rush-delivery-local printf '%s %s\n' \ - '802ed18dc3bce89974d64884fe3c7ca64f3e206faa4c8c8eef237757101bd391' \ + '35e60214455a84ee27078a0e71481565b1a6d8aab53ba90d511cf0d5970afc27' \ rush-delivery-local | sha256sum --check --strict chmod 0755 rush-delivery-local ``` @@ -26,7 +26,7 @@ Keep the launcher and module on the same release: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ workflow \ @@ -41,6 +41,13 @@ The launcher accepts `workflow`, `validate`, and `release-packages`. It owns the local `repo` and source-mode arguments; passing `--repo`, `--source-mode`, or Git source coordinates after `--` is rejected. +For bounded Dagger Shell calls, the launcher converts `--workflow-env-file`, +`--deploy-env-file`, `--release-env-file`, `--runtime-files`, and +`--docker-socket` host paths into typed `host.file`, `host.directory`, and +`host.unix-socket` objects. This keeps absolute host paths stable when the +module checkout and runner temporary directory have different roots. Paths are +escaped as data and are never evaluated as Dagger Shell source. + ## Default Boundary The default `bounded` policy sends these ordered exclusions to Dagger's @@ -106,7 +113,7 @@ default: with: fetch-depth: 0 -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: source-mode: local_copy repo: . @@ -162,7 +169,7 @@ Run once with plain progress and inspect the first source operation: ```sh DAGGER_NO_NAG=1 ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- validate \ --git-sha="$(git rev-parse HEAD)" \ diff --git a/docs/metadata.md b/docs/metadata.md index 1b78d24..c88b1cc 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -10,11 +10,11 @@ For editor integration in external projects, prefer exact versioned schema URLs. For example: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-target.schema.json ``` The root `https://bootstraplaboratory.github.io/rush-delivery/schemas/` URLs -track the current release. Exact paths such as `/schemas/v0.9.0/...` are the +track the current release. Exact paths such as `/schemas/v0.9.1/...` are the stable contract for projects pinned to that Rush Delivery version. ## Package Release @@ -32,7 +32,7 @@ source of truth for package selection, version changes, changelogs, and publishable package rules. ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/npm-release.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/npm-release.schema.json kind: npm @@ -258,7 +258,7 @@ inside that context, a relative image name, one explicit `platform`, and a scanner policy: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: control-plane-api artifact: @@ -289,7 +289,7 @@ Illustrative provider metadata (replace the example registry and namespace with an accepted registry recipe): ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry diff --git a/docs/oci-application-image-troubleshooting.md b/docs/oci-application-image-troubleshooting.md index 6c6b824..78d985d 100644 --- a/docs/oci-application-image-troubleshooting.md +++ b/docs/oci-application-image-troubleshooting.md @@ -1,6 +1,6 @@ # OCI Application-Image Troubleshooting -Use this runbook for Rush Delivery `v0.9.0` OCI Package and digest-only Deploy +Use this runbook for Rush Delivery `v0.9.1` OCI Package and digest-only Deploy failures. The central rule is simple: once registry mutation may have started, do not automatically replay the whole workflow. Inspect the subject, navigation tag, signatures, and attestations first. diff --git a/docs/oci-application-images.md b/docs/oci-application-images.md index 4c8300f..2e42b3f 100644 --- a/docs/oci-application-images.md +++ b/docs/oci-application-images.md @@ -1,6 +1,6 @@ # OCI Application Images -Rush Delivery `v0.9.0` can package a deploy target as a single-platform OCI +Rush Delivery `v0.9.1` can package a deploy target as a single-platform OCI image, publish it, sign and attest the immutable digest, and hand that digest to project-owned Deploy code. This page is the production contract and operator runbook. Follow the [end-to-end tutorial](tutorial/oci-application-images/README.md) @@ -81,10 +81,10 @@ There are three separate trust claims: Declare one artifact in `.dagger/package/targets/.yaml`. The target name must agree with the Rush project, services mesh, package filename, and Deploy target. The complete constraints are in the immutable -[`v0.9.0` package-target schema](../schemas/v0.9.0/package-target.schema.json). +[`v0.9.1` package-target schema](../schemas/v0.9.1/package-target.schema.json). ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: control-plane-api artifact: kind: oci_image @@ -138,10 +138,10 @@ Deploy never consumes that tag. Providers live only at `.dagger/application-images/providers.yaml`. They are independent of source, toolchain-image, Rush-cache, npm, and deployment-platform authentication. See the immutable -[`v0.9.0` provider schema](../schemas/v0.9.0/application-image-providers.schema.json). +[`v0.9.1` provider schema](../schemas/v0.9.1/application-image-providers.schema.json). ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry @@ -556,7 +556,7 @@ registry query and no Cosign operation. ## Manifest Examples The exact schema is -[`package-manifest.schema.json`](../schemas/v0.9.0/package-manifest.schema.json). +[`package-manifest.schema.json`](../schemas/v0.9.1/package-manifest.schema.json). All hashes below are synthetic but full length. ### Legacy filesystem-only manifest @@ -702,7 +702,7 @@ A planned OCI dry-run result has `artifactImage` and `artifactKind` but omits Before the first live publication: -- Pin the Action/module and editor schemas to `v0.9.0`. +- Pin the Action/module and editor schemas to `v0.9.1`. - Validate metadata with `validate-metadata-contract`, then run provider-off and named-provider dry runs. - Use a trusted-TLS registry whose image, signature, and attestation behavior diff --git a/docs/oci-registry-recipes.md b/docs/oci-registry-recipes.md index d192fa0..c4fb3fa 100644 --- a/docs/oci-registry-recipes.md +++ b/docs/oci-registry-recipes.md @@ -1,6 +1,6 @@ # OCI Registry Recipes -This guide maps the Rush Delivery `v0.9.0` application-image provider contract +This guide maps the Rush Delivery `v0.9.1` application-image provider contract to common registries. Start with the production contract in [OCI application images](oci-application-images.md), then complete the [tutorial](tutorial/oci-application-images/README.md) with a disposable @@ -19,7 +19,7 @@ The recipes are deliberately explicit about test status: Production workflow dependencies must be immutable. Third-party actions in this guide use full 40-character commit SHAs with a release-version comment; update the SHA and comment together through reviewed dependency automation. Rush -Delivery examples use `@v0.9.0` to identify this guide's release contract. In a +Delivery examples use `@v0.9.1` to identify this guide's release contract. In a strict consumer workflow, verify that release tag and replace it with the full release commit SHA before merge. GitHub documents that only the full commit SHA is immutable and can enforce full-SHA action references in repository or @@ -71,7 +71,7 @@ prerequisites. Use this shape for a standards-compatible private registry: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry @@ -107,7 +107,7 @@ provider permits it. Give the deployment platform only subject pull/read access. Map credentials without placing values in metadata: ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: application-image-provider: release docker-socket: "" @@ -189,7 +189,7 @@ release-gate endpoint, and record the gate result for the exact release candidate. Do not infer continuous vendor coverage from unit tests. ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: ghcr: kind: oci_registry @@ -218,7 +218,7 @@ permissions: packages: write steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: application-image-provider: ghcr docker-socket: "" @@ -294,7 +294,7 @@ unverified automatic deletion workflow. continuous live Rush Delivery test is claimed. ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: gar: kind: oci_registry @@ -403,7 +403,7 @@ jobs: export_environment_variables: false - name: Publish with Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: application-image-provider: gar docker-socket: "" @@ -470,7 +470,7 @@ deletion instead of assuming a tag cleanup removed everything. continuous live Rush Delivery test is claimed. ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: ecr: kind: oci_registry @@ -615,7 +615,7 @@ afterward. Rush Delivery does not automate deletion. documentation; no continuous live Rush Delivery test is claimed. ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: dockerhub: kind: oci_registry @@ -655,7 +655,7 @@ repository-scoped organization tokens in [Organization access tokens](https://docs.docker.com/enterprise/security/access-tokens/). ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: application-image-provider: dockerhub docker-socket: "" diff --git a/docs/quick-start/ci-cli.md b/docs/quick-start/ci-cli.md index 0ae1d73..5154fd6 100644 --- a/docs/quick-start/ci-cli.md +++ b/docs/quick-start/ci-cli.md @@ -9,7 +9,7 @@ need to mount the repository into the module. For pull-request validation: ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 DEPLOY_ENV_FILE="${RUNNER_TEMP}/dagger-validate.env" SOURCE_REPOSITORY_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" @@ -45,7 +45,7 @@ files. For release workflow runs: ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 RUNTIME_FILES_DIR="${RUNNER_TEMP}/rush-delivery-runtime-files" WORKFLOW_ENV_FILE="${RUNNER_TEMP}/dagger-workflow.env" DEPLOY_ENV_FILE="${RUNNER_TEMP}/dagger-deploy.env" @@ -103,7 +103,7 @@ packaging does not need a host Docker socket. For package release/versioning: ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 RELEASE_ENV_FILE="${RUNNER_TEMP}/dagger-release.env" SOURCE_REPOSITORY_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" diff --git a/docs/quick-start/github-actions.md b/docs/quick-start/github-actions.md index b9df419..f9ac083 100644 --- a/docs/quick-start/github-actions.md +++ b/docs/quick-start/github-actions.md @@ -33,7 +33,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -69,7 +69,7 @@ jobs: service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - name: Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: dry-run: "false" force-targets-json: ${{ inputs.force_targets_json || '[]' }} @@ -132,7 +132,7 @@ jobs: contents: write steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" diff --git a/docs/quick-start/local-run.md b/docs/quick-start/local-run.md index c3fd6f6..1911586 100644 --- a/docs/quick-start/local-run.md +++ b/docs/quick-start/local-run.md @@ -6,7 +6,7 @@ your latest changes. ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ workflow \ @@ -29,7 +29,7 @@ For local PR-style validation only: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ validate \ @@ -41,7 +41,7 @@ For a local package-release dry-run: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ release-packages \ diff --git a/docs/rush-toolchain.md b/docs/rush-toolchain.md index 7da0962..dac8bda 100644 --- a/docs/rush-toolchain.md +++ b/docs/rush-toolchain.md @@ -1,6 +1,6 @@ # Project-Owned Rush Toolchain -Rush Delivery `v0.9.0` lets a repository add deterministic executables to the +Rush Delivery `v0.9.1` lets a repository add deterministic executables to the shared Rush workflow image through `.dagger/toolchains/rush.yaml`. The contract is intentionally narrow: immutable base image, checksummed HTTPS downloads, and fixed executable destinations. It is not a general container build script. @@ -11,12 +11,12 @@ hash, provider cache reference, and provider-off behavior. ## Contract Use the exact versioned -[`rush-toolchain` schema](../schemas/v0.9.0/rush-toolchain.schema.json). The same +[`rush-toolchain` schema](../schemas/v0.9.1/rush-toolchain.schema.json). The same metadata is available as a tested [configuration fragment](../examples/deployment-environment-compatibility/rush-toolchain.yaml): ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-toolchain.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-toolchain.schema.json version: rush-delivery-rush-toolchain/v1 base_image: node:24-bookworm-slim@sha256:65932751ed4073ed02f5c04e494e4b2572a891b7dbea0568a863dc80341bf848 platform: linux/amd64 @@ -98,7 +98,7 @@ They do not become toolchain environment variables and are never hashed. 5. Validate metadata before a build: ```sh - dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call validate-metadata-contract --repo=. ``` diff --git a/docs/tutorial/05-package-targets.md b/docs/tutorial/05-package-targets.md index 899d27a..f2eb156 100644 --- a/docs/tutorial/05-package-targets.md +++ b/docs/tutorial/05-package-targets.md @@ -55,7 +55,7 @@ frontend assets are the common case. Use an OCI artifact when the deploy platform consumes a container image: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: server artifact: diff --git a/docs/tutorial/09-github-actions.md b/docs/tutorial/09-github-actions.md index d17e608..dbbd9e1 100644 --- a/docs/tutorial/09-github-actions.md +++ b/docs/tutorial/09-github-actions.md @@ -14,7 +14,7 @@ permissions: packages: read steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -99,7 +99,7 @@ Package release/versioning can be composed into the main trusted workflow when the same job should deploy applications and release npm packages: ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: dry-run: "false" release-targets-json: '["npm"]' @@ -127,7 +127,7 @@ jobs: contents: write steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" @@ -148,7 +148,7 @@ images use GitHub Container Registry. Pin Rush Delivery to a released tag: ```yaml -uses: BootstrapLaboratory/rush-delivery@v0.9.0 +uses: BootstrapLaboratory/rush-delivery@v0.9.1 ``` Advance the tag intentionally when you want new behavior. Do not use an diff --git a/docs/tutorial/10-local-dry-runs.md b/docs/tutorial/10-local-dry-runs.md index 9243e5d..bfdfe7f 100644 --- a/docs/tutorial/10-local-dry-runs.md +++ b/docs/tutorial/10-local-dry-runs.md @@ -11,7 +11,7 @@ Run the full workflow without publishing provider artifacts or deploying: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- workflow \ --git-sha="$(git rev-parse HEAD)" \ --event-name=manual \ @@ -32,7 +32,7 @@ To exercise one target, force it: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- workflow \ --git-sha="$(git rev-parse HEAD)" \ --event-name=manual \ @@ -54,7 +54,7 @@ To validate local changes against your main branch: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- validate \ --event-name=pull_request \ --pr-base-sha="$(git merge-base HEAD origin/main)" @@ -68,7 +68,7 @@ To test npm release metadata inside the composed workflow without publishing: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- workflow \ --git-sha="$(git rev-parse HEAD)" \ --event-name=manual \ @@ -82,7 +82,7 @@ To test only the standalone npm release entrypoint: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- release-packages \ --git-sha="$(git rev-parse HEAD)" \ --dry-run=true \ diff --git a/docs/tutorial/11-adapting-to-your-project.md b/docs/tutorial/11-adapting-to-your-project.md index 7ddda05..6ea1178 100644 --- a/docs/tutorial/11-adapting-to-your-project.md +++ b/docs/tutorial/11-adapting-to-your-project.md @@ -141,7 +141,7 @@ Next: [NPM Package Release Baseline](12-npm-package-release-baseline.md). For editor validation, point metadata files at exact published schema versions such as -`https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-target.schema.json`. +`https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-target.schema.json`. For an OCI project shape, continue with the [OCI application images tutorial](oci-application-images/README.md), diff --git a/docs/tutorial/13-release-metadata.md b/docs/tutorial/13-release-metadata.md index 2be6647..f52ac20 100644 --- a/docs/tutorial/13-release-metadata.md +++ b/docs/tutorial/13-release-metadata.md @@ -6,7 +6,7 @@ small because Rush and npm already own most package release policy. LabKit uses this shape: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/npm-release.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/npm-release.schema.json kind: npm diff --git a/docs/tutorial/14-package-release-workflow.md b/docs/tutorial/14-package-release-workflow.md index b232c18..cb31d0f 100644 --- a/docs/tutorial/14-package-release-workflow.md +++ b/docs/tutorial/14-package-release-workflow.md @@ -11,7 +11,7 @@ separation. Select package release explicitly: ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: dry-run: "false" release-targets-json: '["npm"]' @@ -63,7 +63,7 @@ jobs: contents: write steps: - name: Run Rush Delivery package release - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" @@ -112,7 +112,7 @@ permissions: packages: read steps: - - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + - uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -129,7 +129,7 @@ Use local-copy source mode to test metadata and release behavior before pushing: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. -- release-packages \ --git-sha="$(git rev-parse HEAD)" \ --dry-run=true \ diff --git a/docs/tutorial/15-mixed-node-python-toolchain.md b/docs/tutorial/15-mixed-node-python-toolchain.md index b19cba8..139927c 100644 --- a/docs/tutorial/15-mixed-node-python-toolchain.md +++ b/docs/tutorial/15-mixed-node-python-toolchain.md @@ -12,7 +12,7 @@ download, extraction, and cache contract. Create `.dagger/toolchains/rush.yaml`: ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-toolchain.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-toolchain.schema.json version: rush-delivery-rush-toolchain/v1 base_image: node:24-bookworm-slim@sha256:65932751ed4073ed02f5c04e494e4b2572a891b7dbea0568a863dc80341bf848 platform: linux/amd64 @@ -51,7 +51,7 @@ project also locks and verifies its own dependencies. ## 3. Validate Before Downloading ```sh -dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call validate-metadata-contract --repo=. ``` @@ -64,7 +64,7 @@ Run the same validation lifecycle without a toolchain registry: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ validate \ @@ -86,7 +86,7 @@ After provider-off succeeds, a trusted release job can populate the normal content-addressed toolchain cache: ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: toolchain-image-provider: github toolchain-image-policy: lazy @@ -97,7 +97,7 @@ content-addressed toolchain cache: Pull requests should keep read-only behavior: ```yaml -- uses: BootstrapLaboratory/rush-delivery@v0.9.0 +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github diff --git a/docs/tutorial/oci-application-images/01-build-and-scan-target.md b/docs/tutorial/oci-application-images/01-build-and-scan-target.md index e05d9fe..3ffb536 100644 --- a/docs/tutorial/oci-application-images/01-build-and-scan-target.md +++ b/docs/tutorial/oci-application-images/01-build-and-scan-target.md @@ -109,7 +109,7 @@ The complete package target is [`control-plane-api.yaml`](../../../examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml): ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: control-plane-api artifact: kind: oci_image @@ -168,7 +168,7 @@ one contained Dockerfile, one image suffix, one platform, trusted source labels, and the documented scan policy. Do not infer support for metadata-driven build arguments, secrets, SSH forwarding, extra contexts, multi-platform indexes, or custom Dockerfile frontends. See the -[package-target schema](../../../schemas/v0.9.0/package-target.schema.json) and +[package-target schema](../../../schemas/v0.9.1/package-target.schema.json) and [OCI application-image contract](../../oci-application-images.md) for the bounded surface. diff --git a/docs/tutorial/oci-application-images/02-provider-off-dry-run.md b/docs/tutorial/oci-application-images/02-provider-off-dry-run.md index e9ef3b6..8475b27 100644 --- a/docs/tutorial/oci-application-images/02-provider-off-dry-run.md +++ b/docs/tutorial/oci-application-images/02-provider-off-dry-run.md @@ -15,7 +15,7 @@ shape-wise but is deliberately not a production source identity. ```bash set -euo pipefail -export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.1" export TUTORIAL_DRY_SHA="0123456789abcdef0123456789abcdef01234567" test "${#TUTORIAL_DRY_SHA}" -eq 40 ``` diff --git a/docs/tutorial/oci-application-images/03-registry-and-cosign-bootstrap.md b/docs/tutorial/oci-application-images/03-registry-and-cosign-bootstrap.md index 21c775c..5044fa9 100644 --- a/docs/tutorial/oci-application-images/03-registry-and-cosign-bootstrap.md +++ b/docs/tutorial/oci-application-images/03-registry-and-cosign-bootstrap.md @@ -27,7 +27,7 @@ tutorial destination: ```yaml # GHCR tutorial template: replace "example" with a normalized owner before use. -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: ghcr: kind: oci_registry @@ -79,7 +79,7 @@ grep -F "repository_prefix: ${GHCR_OWNER}/rush-delivery-tutorial" \ If the owner check fails, normalize the account/organization spelling rather than adding uppercase or an unsupported path to metadata. If validation later reports an invalid provider, compare the complete file with the -[v0.9.0 provider schema](../../../schemas/v0.9.0/application-image-providers.schema.json). +[v0.9.1 provider schema](../../../schemas/v0.9.1/application-image-providers.schema.json). `GHCR_OWNER` is the destination user or organization namespace; `GHCR_USERNAME` is the user that authenticates the token. They are often different when publishing to an organization. diff --git a/docs/tutorial/oci-application-images/06-github-actions.md b/docs/tutorial/oci-application-images/06-github-actions.md index f8ca160..10d018c 100644 --- a/docs/tutorial/oci-application-images/06-github-actions.md +++ b/docs/tutorial/oci-application-images/06-github-actions.md @@ -11,7 +11,7 @@ GitHub treats only a full 40-character commit SHA as an immutable action reference. The third-party actions below are pinned to reviewed full SHAs and retain a release-version comment for dependency updates. Enable Dependabot (or an equivalent reviewed updater) for those pins. Rush Delivery references remain -`@v0.9.0` here so every example states the release contract being taught; a +`@v0.9.1` here so every example states the release contract being taught; a strict production repository should resolve that reviewed release tag, verify it against the release record, and replace the tag with its full commit SHA before merging the workflow. This is required when the repository or @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Plan Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: git-sha: ${{ github.event.pull_request.head.sha }} event-name: workflow_call @@ -105,7 +105,7 @@ jobs: contents: write steps: - name: Publish and deploy verified OCI image - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: git-sha: ${{ github.sha }} event-name: workflow_call @@ -159,7 +159,7 @@ repository guard was not trusted; that is expected on PRs and forks. The composite Action does not expose `package-deploy-targets` or `build-and-package-deploy-targets` as `entrypoint` values. A split-stage job must -install the pinned Dagger CLI, then invoke the `v0.9.0` module directly. This +install the pinned Dagger CLI, then invoke the `v0.9.1` module directly. This complete job publishes and exports the package directory without running Deploy: @@ -214,7 +214,7 @@ jobs: } > "${DEPLOY_ENV_FILE}" dagger \ - -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call build-and-package-deploy-targets \ --repo=. \ --ci-plan-file=ci/oci-plan.json \ diff --git a/docs/tutorial/oci-application-images/07-split-stages-and-rollback.md b/docs/tutorial/oci-application-images/07-split-stages-and-rollback.md index 6fb196d..52cc72d 100644 --- a/docs/tutorial/oci-application-images/07-split-stages-and-rollback.md +++ b/docs/tutorial/oci-application-images/07-split-stages-and-rollback.md @@ -33,7 +33,7 @@ Run these commands from the exact committed source revision: ```bash set -euo pipefail -export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.1" export SOURCE_SHA="$(git rev-parse HEAD)" test "${#SOURCE_SHA}" -eq 40 test "$(git status --porcelain)" = "" diff --git a/docs/tutorial/oci-application-images/08-environment-profiles.md b/docs/tutorial/oci-application-images/08-environment-profiles.md index ec0b56e..0a291d5 100644 --- a/docs/tutorial/oci-application-images/08-environment-profiles.md +++ b/docs/tutorial/oci-application-images/08-environment-profiles.md @@ -16,7 +16,7 @@ repository keeps the same provider and coordinate-only env files under the [deployment compatibility examples](../../../examples/deployment-environment-compatibility): ```yaml -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry @@ -84,7 +84,7 @@ APP_IMAGE_REPOSITORY_PREFIX=example-inc/staging ``` ```sh -dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call build-and-package-deploy-targets \ --repo=. \ --ci-plan-file=ci/oci-plan.json \ @@ -128,7 +128,7 @@ Load staging coordinates and credentials through the selected CI environment, then run live Package: ```sh -dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call build-and-package-deploy-targets \ --repo=. \ --ci-plan-file=ci/oci-plan.json \ @@ -151,7 +151,7 @@ Pass the packaged workspace and its manifest to Deploy. Do not supply a new provider or ask Deploy to read the production profile: ```sh -dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ +dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ call deploy-release \ --repo=staging-package \ --git-sha="$(git rev-parse HEAD)" \ diff --git a/docs/tutorial/oci-application-images/README.md b/docs/tutorial/oci-application-images/README.md index ba307cf..60344a7 100644 --- a/docs/tutorial/oci-application-images/README.md +++ b/docs/tutorial/oci-application-images/README.md @@ -6,7 +6,7 @@ GitHub Actions, split-stage handoff, and rollback. The checked-in [canonical example](../../../examples/oci-application-image-rush-repo) is the single source for the project files used throughout the tutorial. -Rush Delivery `v0.9.0` keeps OCI application images opt-in. A repository whose +Rush Delivery `v0.9.1` keeps OCI application images opt-in. A repository whose selected package artifacts are only `directory` or `rush_deploy_archive` does not need an application-image provider or OCI credentials. @@ -37,11 +37,11 @@ state from another checkout. set -euo pipefail TUTORIAL_PARENT="${TMPDIR:-/tmp}/rush-delivery-oci-tutorial" -SOURCE_CHECKOUT="${TMPDIR:-/tmp}/rush-delivery-v0.9.0-source" +SOURCE_CHECKOUT="${TMPDIR:-/tmp}/rush-delivery-v0.9.1-source" test ! -e "${TUTORIAL_PARENT}" test ! -e "${SOURCE_CHECKOUT}" -git clone --depth=1 --branch=v0.9.0 \ +git clone --depth=1 --branch=v0.9.1 \ https://github.com/BootstrapLaboratory/rush-delivery.git \ "${SOURCE_CHECKOUT}" mkdir -p "${TUTORIAL_PARENT}" @@ -56,15 +56,15 @@ git config user.email "rush-delivery-tutorial@example.invalid" git add --all git commit -m "chore: initialize OCI image tutorial" -export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.0" +export RUSH_DELIVERY_MODULE="github.com/BootstrapLaboratory/rush-delivery@v0.9.1" export TUTORIAL_REPOSITORY="${TUTORIAL_PARENT}" -export RUSH_DELIVERY_LOCAL="${TMPDIR:-/tmp}/rush-delivery-local-v0.9.0" +export RUSH_DELIVERY_LOCAL="${TMPDIR:-/tmp}/rush-delivery-local-v0.9.1" curl --fail --location \ --output "${RUSH_DELIVERY_LOCAL}" \ - https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.0/rush-delivery-local + https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.1/rush-delivery-local printf '%s %s\n' \ - '802ed18dc3bce89974d64884fe3c7ca64f3e206faa4c8c8eef237757101bd391' \ + '35e60214455a84ee27078a0e71481565b1a6d8aab53ba90d511cf0d5970afc27' \ "${RUSH_DELIVERY_LOCAL}" | sha256sum --check --strict chmod 0755 "${RUSH_DELIVERY_LOCAL}" ``` @@ -72,12 +72,12 @@ chmod 0755 "${RUSH_DELIVERY_LOCAL}" Sanitized expected output: ```text -Cloning into '/tmp/rush-delivery-v0.9.0-source'... +Cloning into '/tmp/rush-delivery-v0.9.1-source'... Initialized empty Git repository in /tmp/rush-delivery-oci-tutorial/.git/ [main (root-commit) ] chore: initialize OCI image tutorial ``` -If `git clone` cannot resolve `v0.9.0`, the release has not been published to +If `git clone` cannot resolve `v0.9.1`, the release has not been published to the selected remote. If `git commit` reports no files, confirm that `examples/oci-application-image-rush-repo` exists in that tag and that GNU or compatible `tar` honored `--strip-components=2`. diff --git a/docs/upgrade-v0.9.0.md b/docs/upgrade-v0.9.0.md index 22e739b..98f95fb 100644 --- a/docs/upgrade-v0.9.0.md +++ b/docs/upgrade-v0.9.0.md @@ -47,17 +47,17 @@ evidence explicitly. Update Action and module references: ```yaml -uses: BootstrapLaboratory/rush-delivery@v0.9.0 +uses: BootstrapLaboratory/rush-delivery@v0.9.1 ``` ```sh -RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 ``` -Use exact v0.9.0 editor schemas: +Use exact `v0.9.1` editor schemas when adopting the current patch: ```text -https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/.schema.json +https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/.schema.json ``` If local worktrees are used, install the checksummed launcher exactly as shown diff --git a/docs/upgrade-v0.9.1.md b/docs/upgrade-v0.9.1.md new file mode 100644 index 0000000..7259330 --- /dev/null +++ b/docs/upgrade-v0.9.1.md @@ -0,0 +1,94 @@ +# Upgrade From v0.9.0 To v0.9.1 + +Rush Delivery `v0.9.1` is a compatibility patch for bounded local-copy runs +through the GitHub Action. It does not change `.dagger` metadata, Dagger +entrypoints, package manifests, provider activation, OCI publication, or Rush +toolchain behavior. + +## Who Must Upgrade + +Upgrade if a workflow uses all three of these settings: + +- the Rush Delivery Action is pinned to `v0.9.0`; +- `source-mode: local_copy`; and +- `source-import-policy: bounded`, including its default value. + +That `v0.9.0` Action combination can fail before Dagger starts because its +generated Dagger Shell contains quoted exclusion patterns that cannot safely +cross the pinned `dagger-for-github` shell-input transport. The standalone +`rush-delivery-local` release asset, the remote Dagger module, Git source mode, +and explicit `source-import-policy: legacy` are not affected. + +## Upgrade + +Change only the Rush Delivery pin: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 + with: + source-mode: local_copy + source-import-policy: bounded +``` + +For direct module or launcher use, keep both components on the same patch: + +```sh +RUSH_DELIVERY_MODULE=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 +RUSH_DELIVERY_LOCAL_URL=https://github.com/BootstrapLaboratory/rush-delivery/releases/download/v0.9.1/rush-delivery-local +``` + +The `v0.9.1` launcher also materializes env files, runtime directories, and the +optional Docker socket as typed host objects before calling the module. Its +checksum therefore differs from `v0.9.0`; always verify the checksum published +for `v0.9.1`. The release asset remains byte-identical to the launcher bundled +with the `v0.9.1` Action. + +No `.dagger/source-import.ignore` edit is required. Existing static providers, +environment-backed coordinates, provider-off behavior, and project-owned Rush +toolchains keep the `v0.9.0` contract. Exact editor schemas may be advanced to +the byte-equivalent `v0.9.1` snapshot: + +```text +https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/.schema.json +``` + +## Canary + +Run one non-publishing workflow with the same source mode and import policy as +production: + +```yaml +- uses: BootstrapLaboratory/rush-delivery@v0.9.1 + with: + source-mode: local_copy + source-import-policy: bounded + application-image-provider: off + toolchain-image-provider: off + rush-cache-provider: off + dry-run: true +``` + +Confirm that: + +- the Action reaches Dagger instead of failing while assembling its command; +- excluded dependency/cache trees are absent from the imported directory; +- every required re-inclusion from `.dagger/source-import.ignore` is present; +- filesystem-only and provider-off OCI plans match the `v0.9.0` result; and +- no registry, deploy-tag, or package-release mutation occurs. + +Then promote the unchanged `v0.9.1` pin to trusted release workflows. + +## Recovery + +If the canary fails inside project Build, Package, or Deploy logic, return to +the last successful pin and compare the generated plan; the patch does not +change those stages. If bounded filtering removed a required generated path, +add the narrowest `!` inclusion described in the +[bounded local-copy guide](local-copy-source-imports.md). + +`source-import-policy: legacy` remains an explicit short-term transfer fallback, +but it restores the larger `v0.8.1` import boundary. Do not move or recreate the +immutable `v0.9.0` tag; pin `v0.9.1` for the corrected bounded Action path. + +For the feature-level migration from `v0.8.1`, continue with the +[v0.9.0 upgrade guide](upgrade-v0.9.0.md). diff --git a/docs/workflows.md b/docs/workflows.md index cfeb6ab..8979491 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -15,7 +15,7 @@ a Docker socket against local unpushed changes: ```sh ./rush-delivery-local \ - --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.0 \ + --module=github.com/BootstrapLaboratory/rush-delivery@v0.9.1 \ --repo=. \ -- \ workflow \ @@ -42,7 +42,7 @@ For GitHub Actions, prefer the repository action wrapper: ```yaml - name: Rush Delivery - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: force-targets-json: ${{ inputs.force_targets_json || '[]' }} environment: prod @@ -65,7 +65,7 @@ change files: ```yaml - name: Rush Delivery validation - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: validate toolchain-image-provider: github @@ -84,7 +84,7 @@ adapters off: ```yaml - name: Rush Delivery package release - uses: BootstrapLaboratory/rush-delivery@v0.9.0 + uses: BootstrapLaboratory/rush-delivery@v0.9.1 with: entrypoint: release-packages dry-run: "false" diff --git a/examples/deployment-environment-compatibility/README.md b/examples/deployment-environment-compatibility/README.md index 3f7c6a8..6db9456 100644 --- a/examples/deployment-environment-compatibility/README.md +++ b/examples/deployment-environment-compatibility/README.md @@ -1,6 +1,6 @@ # Deployment Environment Compatibility Examples -These are credential-free `v0.9.0` configuration fragments for the three +These are credential-free `v0.9.1` configuration fragments for the three opt-in contracts: - copy `application-image-providers.yaml` to diff --git a/examples/deployment-environment-compatibility/application-image-providers.yaml b/examples/deployment-environment-compatibility/application-image-providers.yaml index 9961fe5..c900470 100644 --- a/examples/deployment-environment-compatibility/application-image-providers.yaml +++ b/examples/deployment-environment-compatibility/application-image-providers.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry diff --git a/examples/deployment-environment-compatibility/rush-toolchain.yaml b/examples/deployment-environment-compatibility/rush-toolchain.yaml index bcf85c9..ba1a650 100644 --- a/examples/deployment-environment-compatibility/rush-toolchain.yaml +++ b/examples/deployment-environment-compatibility/rush-toolchain.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-toolchain.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-toolchain.schema.json version: rush-delivery-rush-toolchain/v1 base_image: node:24-bookworm-slim@sha256:65932751ed4073ed02f5c04e494e4b2572a891b7dbea0568a863dc80341bf848 platform: linux/amd64 diff --git a/examples/oci-application-image-rush-repo/.dagger/application-images/providers.yaml b/examples/oci-application-image-rush-repo/.dagger/application-images/providers.yaml index eaef6a6..76399bc 100644 --- a/examples/oci-application-image-rush-repo/.dagger/application-images/providers.yaml +++ b/examples/oci-application-image-rush-repo/.dagger/application-images/providers.yaml @@ -1,5 +1,5 @@ # GHCR tutorial template: replace "example" with a normalized owner before use. -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: ghcr: kind: oci_registry diff --git a/examples/oci-application-image-rush-repo/.dagger/deploy/services-mesh.yaml b/examples/oci-application-image-rush-repo/.dagger/deploy/services-mesh.yaml index 11578f8..e6192f7 100644 --- a/examples/oci-application-image-rush-repo/.dagger/deploy/services-mesh.yaml +++ b/examples/oci-application-image-rush-repo/.dagger/deploy/services-mesh.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-services-mesh.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-services-mesh.schema.json services: control-plane-api: deploy_after: [] diff --git a/examples/oci-application-image-rush-repo/.dagger/deploy/targets/control-plane-api.yaml b/examples/oci-application-image-rush-repo/.dagger/deploy/targets/control-plane-api.yaml index b44966d..2b879f7 100644 --- a/examples/oci-application-image-rush-repo/.dagger/deploy/targets/control-plane-api.yaml +++ b/examples/oci-application-image-rush-repo/.dagger/deploy/targets/control-plane-api.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/deploy-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-target.schema.json name: control-plane-api deploy_script: deploy/consume-image.sh runtime: diff --git a/examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml b/examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml index e6a3a77..56a708b 100644 --- a/examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml +++ b/examples/oci-application-image-rush-repo/.dagger/package/targets/control-plane-api.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: control-plane-api artifact: kind: oci_image diff --git a/examples/oci-application-image-rush-repo/.dagger/rush-cache/providers.yaml b/examples/oci-application-image-rush-repo/.dagger/rush-cache/providers.yaml index 825d909..ee9f21e 100644 --- a/examples/oci-application-image-rush-repo/.dagger/rush-cache/providers.yaml +++ b/examples/oci-application-image-rush-repo/.dagger/rush-cache/providers.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/rush-cache-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-cache-providers.schema.json cache: version: v1 paths: diff --git a/schemas/v0.9.1/application-image-providers.schema.json b/schemas/v0.9.1/application-image-providers.schema.json new file mode 100644 index 0000000..cba3e6f --- /dev/null +++ b/schemas/v0.9.1/application-image-providers.schema.json @@ -0,0 +1,107 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json", + "title": "Rush Delivery application image providers", + "description": "Credential environment names must be globally unique across every field and provider; this dynamic invariant is enforced by Rush Delivery metadata validation.", + "type": "object", + "required": ["providers"], + "additionalProperties": false, + "properties": { + "providers": { + "type": "object", + "propertyNames": { + "allOf": [ + { + "pattern": "^[a-z][a-z0-9_-]*$" + }, + { + "not": { + "const": "off" + } + } + ] + }, + "additionalProperties": { + "$ref": "#/$defs/ociRegistryProvider" + } + } + }, + "$defs": { + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "ociRegistryProvider": { + "type": "object", + "description": "All five credential environment names are distinct roles and must also be unique across other declared providers.", + "required": [ + "kind", + "signing_key_env", + "signing_password_env", + "token_env", + "username_env", + "verification_key_env" + ], + "additionalProperties": false, + "allOf": [ + { + "oneOf": [ + { + "required": ["registry"] + }, + { + "required": ["registry_env"] + } + ] + }, + { + "oneOf": [ + { + "required": ["repository_prefix"] + }, + { + "required": ["repository_prefix_env"] + } + ] + } + ], + "properties": { + "kind": { + "const": "oci_registry" + }, + "registry": { + "type": "string", + "pattern": "^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?$" + }, + "registry_env": { + "$ref": "#/$defs/envName" + }, + "repository_prefix": { + "$ref": "#/$defs/repositoryPath" + }, + "repository_prefix_env": { + "$ref": "#/$defs/envName" + }, + "signing_key_env": { + "$ref": "#/$defs/envName" + }, + "signing_password_env": { + "$ref": "#/$defs/envName" + }, + "token_env": { + "$ref": "#/$defs/envName" + }, + "username_env": { + "$ref": "#/$defs/envName" + }, + "verification_key_env": { + "$ref": "#/$defs/envName" + } + } + }, + "repositoryPath": { + "type": "string", + "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$" + } + } +} diff --git a/schemas/v0.9.1/deploy-services-mesh.schema.json b/schemas/v0.9.1/deploy-services-mesh.schema.json new file mode 100644 index 0000000..a1588e8 --- /dev/null +++ b/schemas/v0.9.1/deploy-services-mesh.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-services-mesh.schema.json", + "title": "Rush Delivery deploy services mesh", + "type": "object", + "required": ["services"], + "additionalProperties": false, + "properties": { + "services": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "deploy_after": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + } + } + } + } +} diff --git a/schemas/v0.9.1/deploy-target.schema.json b/schemas/v0.9.1/deploy-target.schema.json new file mode 100644 index 0000000..9c92777 --- /dev/null +++ b/schemas/v0.9.1/deploy-target.schema.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/deploy-target.schema.json", + "title": "Rush Delivery deploy target", + "type": "object", + "required": ["deploy_script", "name", "runtime"], + "additionalProperties": false, + "properties": { + "deploy_script": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "runtime": { + "type": "object", + "required": ["image"], + "additionalProperties": false, + "properties": { + "dry_run_defaults": { + "$ref": "#/$defs/envMap" + }, + "env": { + "$ref": "#/$defs/envMap" + }, + "file_mounts": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": ["source_var", "target"], + "additionalProperties": false, + "properties": { + "source_var": { + "$ref": "#/$defs/projectEnvName" + }, + "target": { + "$ref": "#/$defs/fileMountTarget" + } + } + }, + { + "type": "object", + "required": ["source"], + "additionalProperties": false, + "properties": { + "source": { + "$ref": "#/$defs/repoPath" + }, + "target": { + "$ref": "#/$defs/fileMountTarget" + } + } + } + ] + } + }, + "image": { + "type": "string", + "minLength": 1 + }, + "install": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "map_env": { + "$ref": "#/$defs/envNameMap" + }, + "pass_env": { + "$ref": "#/$defs/envNameList" + }, + "required_host_env": { + "$ref": "#/$defs/envNameList" + }, + "workspace": { + "type": "object", + "additionalProperties": false, + "properties": { + "dirs": { + "$ref": "#/$defs/repoPathList" + }, + "files": { + "$ref": "#/$defs/repoPathList" + }, + "mode": { + "const": "full" + } + } + } + } + } + }, + "$defs": { + "envMap": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/projectEnvName" + }, + "additionalProperties": { + "type": "string" + } + }, + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "envNameList": { + "type": "array", + "items": { + "$ref": "#/$defs/projectEnvName" + }, + "uniqueItems": true + }, + "envNameMap": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/projectEnvName" + }, + "additionalProperties": { + "$ref": "#/$defs/envName" + } + }, + "fileMountTarget": { + "type": "string", + "minLength": 1, + "not": { + "anyOf": [ + { + "pattern": "^/$" + }, + { + "pattern": "^/workspace(?:/\\.dagger(?:/runtime(?:/evidence(?:/.*)?)?)?)?$" + }, + { + "pattern": "^\\.dagger(?:/runtime(?:/evidence(?:/.*)?)?)?$" + } + ] + } + }, + "projectEnvName": { + "allOf": [ + { + "$ref": "#/$defs/envName" + }, + { + "not": { + "anyOf": [ + { + "const": "GIT_SHA" + }, + { + "const": "DRY_RUN" + }, + { + "type": "string", + "pattern": "^ARTIFACT_" + } + ] + } + } + ] + }, + "repoPath": { + "type": "string", + "minLength": 1, + "not": { + "anyOf": [ + { + "pattern": "^/" + }, + { + "pattern": "(^|/)\\.\\.(/|$)" + } + ] + } + }, + "repoPathList": { + "type": "array", + "items": { + "$ref": "#/$defs/repoPath" + }, + "uniqueItems": true + } + } +} diff --git a/schemas/v0.9.1/npm-release.schema.json b/schemas/v0.9.1/npm-release.schema.json new file mode 100644 index 0000000..d1bbaf3 --- /dev/null +++ b/schemas/v0.9.1/npm-release.schema.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/npm-release.schema.json", + "title": "Rush Delivery NPM release", + "type": "object", + "required": ["auth", "kind", "versioning"], + "additionalProperties": false, + "properties": { + "auth": { + "type": "object", + "required": ["kind", "token_env"], + "additionalProperties": false, + "properties": { + "kind": { + "const": "token" + }, + "token_env": { + "$ref": "#/$defs/envName" + } + } + }, + "kind": { + "const": "npm" + }, + "publish": { + "type": "object", + "additionalProperties": false, + "properties": { + "access": { + "enum": ["public", "restricted"] + }, + "provenance": { + "default": false, + "type": "boolean" + }, + "registry": { + "type": "string", + "minLength": 1 + }, + "tag": { + "$ref": "#/$defs/safeCliValue" + } + } + }, + "versioning": { + "type": "object", + "required": ["strategy", "target_branch"], + "additionalProperties": false, + "properties": { + "strategy": { + "const": "rush-change-files" + }, + "target_branch": { + "$ref": "#/$defs/safeGitBranch" + } + } + } + }, + "$defs": { + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "safeCliValue": { + "type": "string", + "minLength": 1, + "not": { + "anyOf": [ + { + "type": "string", + "pattern": "\\s" + }, + { + "type": "string", + "pattern": "^-" + } + ] + } + }, + "safeGitBranch": { + "allOf": [ + { + "$ref": "#/$defs/safeCliValue" + }, + { + "not": { + "anyOf": [ + { + "type": "string", + "pattern": "\\.\\." + }, + { + "type": "string", + "pattern": "\\.lock$" + } + ] + } + } + ] + } + } +} diff --git a/schemas/v0.9.1/package-manifest.schema.json b/schemas/v0.9.1/package-manifest.schema.json new file mode 100644 index 0000000..2834427 --- /dev/null +++ b/schemas/v0.9.1/package-manifest.schema.json @@ -0,0 +1,281 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-manifest.schema.json", + "title": "Rush Delivery package manifest v2", + "type": "object", + "required": ["schema_version", "artifacts"], + "additionalProperties": false, + "properties": { + "schema_version": { + "const": "rush-delivery-package-manifest/v2" + }, + "artifacts": { + "type": "object", + "patternProperties": { + "^(?:\\.{1,2}|(?![A-Za-z0-9@._-]+$).*)$": { + "not": { + "type": "object", + "required": ["kind"], + "properties": { + "kind": { + "const": "oci_image" + } + } + } + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/filesystemArtifact" + }, + { + "$ref": "#/$defs/plannedOciArtifact" + }, + { + "$ref": "#/$defs/publishedOciArtifact" + } + ] + } + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^sha256:[a-f0-9]{64}$" + }, + "evidenceDocument": { + "type": "object", + "required": ["digest", "format", "path", "subject_digest"], + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/digest" + }, + "format": { + "type": "string", + "minLength": 1 + }, + "path": { + "$ref": "#/$defs/evidencePath" + }, + "subject_digest": { + "$ref": "#/$defs/digest" + } + } + }, + "filesystemArtifact": { + "type": "object", + "required": ["deploy_path", "kind", "path"], + "additionalProperties": false, + "properties": { + "deploy_path": { + "$ref": "#/$defs/repositoryPath" + }, + "kind": { + "enum": ["archive", "directory"] + }, + "path": { + "$ref": "#/$defs/repositoryPath" + } + } + }, + "evidencePath": { + "type": "string", + "pattern": "^(?!.*(?:^|/)\\.\\.?(?:/|$))(?!.*//)(?!.*\\\\)\\.dagger/runtime/evidence/(?!\\.{1,2}(?:/|$))[A-Za-z0-9@._-]+/[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$" + }, + "image": { + "type": "string", + "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$" + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + }, + "platforms": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z0-9]+/[a-z0-9_]+(?:/[a-z0-9._-]+)?$" + } + }, + "plannedOciArtifact": { + "type": "object", + "required": ["image", "kind", "platforms", "source_revision", "status"], + "additionalProperties": false, + "properties": { + "image": { + "$ref": "#/$defs/image" + }, + "kind": { + "const": "oci_image" + }, + "platforms": { + "$ref": "#/$defs/platforms" + }, + "repository": { + "$ref": "#/$defs/repository" + }, + "source_revision": { + "$ref": "#/$defs/sourceRevision" + }, + "status": { + "const": "planned" + } + } + }, + "publishedOciArtifact": { + "type": "object", + "required": [ + "digest", + "evidence", + "image", + "kind", + "platforms", + "reference", + "repository", + "source_revision", + "status" + ], + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/digest" + }, + "evidence": { + "$ref": "#/$defs/publishedEvidence" + }, + "image": { + "$ref": "#/$defs/image" + }, + "kind": { + "const": "oci_image" + }, + "platforms": { + "$ref": "#/$defs/platforms" + }, + "reference": { + "type": "string", + "pattern": "^[^@]+@sha256:[a-f0-9]{64}$" + }, + "repository": { + "$ref": "#/$defs/repository" + }, + "source_revision": { + "$ref": "#/$defs/sourceRevision" + }, + "status": { + "const": "published" + } + } + }, + "publishedEvidence": { + "type": "object", + "required": ["provenance", "sbom", "scan", "signature"], + "additionalProperties": false, + "properties": { + "provenance": { + "$ref": "#/$defs/provenanceEvidenceDocument" + }, + "sbom": { + "$ref": "#/$defs/sbomEvidenceDocument" + }, + "scan": { + "$ref": "#/$defs/scanEvidence" + }, + "signature": { + "$ref": "#/$defs/signatureEvidence" + } + } + }, + "repository": { + "type": "string", + "pattern": "^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?/[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$" + }, + "repositoryPath": { + "type": "string", + "pattern": "^(?!/)(?![A-Za-z]:[\\\\/])(?!.*(?:^|/)\\.\\.?(?:/|$))(?!.*//)(?!.*\\\\)(?!.*\\/$).+$" + }, + "provenanceEvidenceDocument": { + "allOf": [ + { + "$ref": "#/$defs/evidenceDocument" + }, + { + "type": "object", + "properties": { + "format": { + "const": "slsa-provenance-v1" + } + } + } + ] + }, + "scanEvidence": { + "type": "object", + "required": ["digest", "path", "policy", "result", "scanner"], + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/digest" + }, + "path": { + "$ref": "#/$defs/evidencePath" + }, + "policy": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": ["critical", "high", "medium", "low", "negligible"] + } + }, + "result": { + "const": "passed" + }, + "scanner": { + "$ref": "#/$defs/nonEmptyString" + } + } + }, + "signatureEvidence": { + "type": "object", + "required": ["kind", "reference", "verified"], + "additionalProperties": false, + "properties": { + "kind": { + "const": "sigstore" + }, + "reference": { + "type": "string", + "pattern": "^[^@]+@sha256:[a-f0-9]{64}$" + }, + "verified": { + "const": true + } + } + }, + "sbomEvidenceDocument": { + "allOf": [ + { + "$ref": "#/$defs/evidenceDocument" + }, + { + "type": "object", + "properties": { + "format": { + "const": "spdx-json" + } + } + } + ] + }, + "sourceRevision": { + "type": "string", + "pattern": "^[a-f0-9]{40}$" + } + } +} diff --git a/schemas/v0.9.1/package-target.schema.json b/schemas/v0.9.1/package-target.schema.json new file mode 100644 index 0000000..d25e7ad --- /dev/null +++ b/schemas/v0.9.1/package-target.schema.json @@ -0,0 +1,196 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json", + "title": "Rush Delivery package target", + "type": "object", + "required": ["artifact", "name"], + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "artifact": { + "type": "object", + "required": ["kind"], + "properties": { + "kind": { + "const": "oci_image" + } + } + } + } + }, + "then": { + "properties": { + "name": { + "$ref": "#/$defs/ociTargetName" + } + } + } + } + ], + "properties": { + "artifact": { + "oneOf": [ + { + "type": "object", + "required": ["kind", "path"], + "additionalProperties": false, + "properties": { + "kind": { + "const": "directory" + }, + "path": { + "$ref": "#/$defs/nonEmptyString" + } + } + }, + { + "type": "object", + "required": ["kind", "output", "project", "scenario"], + "additionalProperties": false, + "properties": { + "kind": { + "const": "rush_deploy_archive" + }, + "output": { + "$ref": "#/$defs/nonEmptyString" + }, + "project": { + "$ref": "#/$defs/nonEmptyString" + }, + "scenario": { + "$ref": "#/$defs/nonEmptyString" + } + } + }, + { + "type": "object", + "required": [ + "kind", + "context", + "dockerfile", + "image", + "platform", + "scan" + ], + "additionalProperties": false, + "properties": { + "kind": { + "const": "oci_image" + }, + "context": { + "$ref": "#/$defs/repositoryPathOrRoot" + }, + "dockerfile": { + "$ref": "#/$defs/repositoryPath" + }, + "image": { + "$ref": "#/$defs/ociImageName" + }, + "platform": { + "$ref": "#/$defs/ociPlatform" + }, + "scan": { + "type": "object", + "required": ["fail_on"], + "additionalProperties": false, + "properties": { + "fail_on": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": ["critical", "high", "medium", "low", "negligible"] + } + }, + "ignore_file": { + "$ref": "#/$defs/repositoryPath" + } + } + } + } + } + ] + }, + "build": { + "type": "object", + "additionalProperties": false, + "properties": { + "dry_run_defaults": { + "$ref": "#/$defs/envMap" + }, + "map_env": { + "$ref": "#/$defs/envNameMap" + }, + "pass_env": { + "$ref": "#/$defs/envNameList" + } + } + }, + "name": { + "$ref": "#/$defs/nonEmptyString" + } + }, + "$defs": { + "envMap": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/envName" + }, + "additionalProperties": { + "type": "string" + } + }, + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "envNameList": { + "type": "array", + "items": { + "$ref": "#/$defs/envName" + }, + "uniqueItems": true + }, + "envNameMap": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/envName" + }, + "additionalProperties": { + "$ref": "#/$defs/envName" + } + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + }, + "ociImageName": { + "type": "string", + "pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$" + }, + "ociPlatform": { + "type": "string", + "pattern": "^[a-z0-9]+/[a-z0-9_]+(?:/[a-z0-9._-]+)?$" + }, + "ociTargetName": { + "type": "string", + "pattern": "^(?!\\.{1,2}$)[A-Za-z0-9@._-]+$" + }, + "repositoryPath": { + "type": "string", + "pattern": "^(?!/)(?![A-Za-z]:/)(?!.*(?:^|/)\\.\\.?(?:/|$))(?!.*//)(?!.*\\/$)[^\\\\]+$" + }, + "repositoryPathOrRoot": { + "anyOf": [ + { + "const": "." + }, + { + "$ref": "#/$defs/repositoryPath" + } + ] + } + } +} diff --git a/schemas/v0.9.1/rush-cache-providers.schema.json b/schemas/v0.9.1/rush-cache-providers.schema.json new file mode 100644 index 0000000..a5e847b --- /dev/null +++ b/schemas/v0.9.1/rush-cache-providers.schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-cache-providers.schema.json", + "title": "Rush Delivery Rush cache providers", + "type": "object", + "required": ["cache", "providers"], + "additionalProperties": false, + "properties": { + "cache": { + "type": "object", + "required": ["paths", "version"], + "additionalProperties": false, + "properties": { + "paths": { + "$ref": "#/$defs/repoPathList" + }, + "version": { + "$ref": "#/$defs/ociTag" + } + } + }, + "providers": { + "type": "object", + "additionalProperties": false, + "properties": { + "github": { + "$ref": "#/$defs/githubRegistryProvider" + } + } + } + }, + "$defs": { + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "githubRegistryProvider": { + "type": "object", + "required": ["kind", "repository_env", "token_env", "username_env"], + "additionalProperties": false, + "properties": { + "image_namespace": { + "$ref": "#/$defs/nonEmptyString" + }, + "kind": { + "const": "github_container_registry" + }, + "registry": { + "$ref": "#/$defs/nonEmptyString" + }, + "repository_env": { + "$ref": "#/$defs/envName" + }, + "token_env": { + "$ref": "#/$defs/envName" + }, + "username_env": { + "$ref": "#/$defs/envName" + } + } + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + }, + "ociTag": { + "type": "string", + "pattern": "^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$" + }, + "repoPath": { + "type": "string", + "minLength": 1, + "not": { + "anyOf": [ + { + "pattern": "^/" + }, + { + "pattern": "(^|/)\\.\\.(/|$)" + } + ] + } + }, + "repoPathList": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/repoPath" + }, + "uniqueItems": true + } + } +} diff --git a/schemas/v0.9.1/rush-toolchain.schema.json b/schemas/v0.9.1/rush-toolchain.schema.json new file mode 100644 index 0000000..d81643a --- /dev/null +++ b/schemas/v0.9.1/rush-toolchain.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/rush-toolchain.schema.json", + "title": "Rush Delivery project-owned Rush toolchain", + "type": "object", + "required": ["version", "base_image", "platform", "downloads"], + "additionalProperties": false, + "properties": { + "version": { + "const": "rush-delivery-rush-toolchain/v1" + }, + "base_image": { + "type": "string", + "pattern": "^(?:[a-z0-9][a-z0-9._-]*(?::[1-9][0-9]{0,4})?/)*(?:[a-z0-9][a-z0-9._-]*)(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?@sha256:[a-f0-9]{64}$" + }, + "platform": { + "const": "linux/amd64" + }, + "downloads": { + "type": "array", + "minItems": 1, + "maxItems": 16, + "items": { + "$ref": "#/$defs/download" + } + } + }, + "$defs": { + "download": { + "type": "object", + "required": ["url", "sha256", "format", "destination", "mode"], + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "pattern": "^https://[^/?#@]+(?:/[^?#]*)?$" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "format": { + "enum": ["raw", "tar_gz"] + }, + "archive_path": { + "type": "string", + "pattern": "^(?![-/])(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//)(?!.*\\\\)[^\\u0000-\\u001f\\u007f]+$" + }, + "destination": { + "type": "string", + "pattern": "^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._+-]*$" + }, + "mode": { + "const": "0755" + } + }, + "allOf": [ + { + "if": { + "properties": { + "format": { + "const": "tar_gz" + } + }, + "required": ["format"] + }, + "then": { + "required": ["archive_path"] + }, + "else": { + "not": { + "required": ["archive_path"] + } + } + } + ] + } + } +} diff --git a/schemas/v0.9.1/toolchain-image-providers.schema.json b/schemas/v0.9.1/toolchain-image-providers.schema.json new file mode 100644 index 0000000..3baec82 --- /dev/null +++ b/schemas/v0.9.1/toolchain-image-providers.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/toolchain-image-providers.schema.json", + "title": "Rush Delivery toolchain image providers", + "type": "object", + "required": ["providers"], + "additionalProperties": false, + "properties": { + "providers": { + "type": "object", + "additionalProperties": false, + "properties": { + "github": { + "$ref": "#/$defs/githubRegistryProvider" + } + } + } + }, + "$defs": { + "envName": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "githubRegistryProvider": { + "type": "object", + "required": ["kind", "repository_env", "token_env", "username_env"], + "additionalProperties": false, + "properties": { + "image_namespace": { + "$ref": "#/$defs/nonEmptyString" + }, + "kind": { + "const": "github_container_registry" + }, + "registry": { + "$ref": "#/$defs/nonEmptyString" + }, + "repository_env": { + "$ref": "#/$defs/envName" + }, + "token_env": { + "$ref": "#/$defs/envName" + }, + "username_env": { + "$ref": "#/$defs/envName" + } + } + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + } + } +} diff --git a/schemas/v0.9.1/validation-target.schema.json b/schemas/v0.9.1/validation-target.schema.json new file mode 100644 index 0000000..4a3795d --- /dev/null +++ b/schemas/v0.9.1/validation-target.schema.json @@ -0,0 +1,121 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/validation-target.schema.json", + "title": "Rush Delivery validation target", + "type": "object", + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": { + "$ref": "#/$defs/identifier" + }, + "services": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/identifier" + }, + "additionalProperties": { + "type": "object", + "required": ["image"], + "additionalProperties": false, + "properties": { + "env": { + "$ref": "#/$defs/stringMap" + }, + "image": { + "$ref": "#/$defs/nonEmptyString" + }, + "ports": { + "$ref": "#/$defs/ports" + } + } + } + }, + "steps": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "required": ["command", "name"], + "additionalProperties": false, + "properties": { + "args": { + "$ref": "#/$defs/stringList" + }, + "command": { + "$ref": "#/$defs/nonEmptyString" + }, + "env": { + "$ref": "#/$defs/stringMap" + }, + "name": { + "$ref": "#/$defs/identifier" + } + } + }, + { + "type": "object", + "required": ["name", "service"], + "additionalProperties": false, + "properties": { + "name": { + "$ref": "#/$defs/identifier" + }, + "service": { + "type": "object", + "required": ["command"], + "additionalProperties": false, + "properties": { + "args": { + "$ref": "#/$defs/stringList" + }, + "command": { + "$ref": "#/$defs/nonEmptyString" + }, + "env": { + "$ref": "#/$defs/stringMap" + }, + "ports": { + "$ref": "#/$defs/ports" + } + } + } + } + } + ] + } + } + }, + "$defs": { + "identifier": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$" + }, + "nonEmptyString": { + "type": "string", + "minLength": 1 + }, + "ports": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "uniqueItems": true + }, + "stringList": { + "type": "array", + "items": { + "$ref": "#/$defs/nonEmptyString" + } + }, + "stringMap": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/src/application-images/package-image.ts b/src/application-images/package-image.ts index 022f44e..812bcd5 100644 --- a/src/application-images/package-image.ts +++ b/src/application-images/package-image.ts @@ -245,7 +245,7 @@ function formatProvenance( { buildDefinition: { buildType: - "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.0", + "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.1", externalParameters: { context: prepared.context, dockerfile: prepared.dockerfile, @@ -266,7 +266,7 @@ function formatProvenance( }, runDetails: { builder: { - id: "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.0", + id: "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.1", }, metadata: { invocationId: `${prepared.target}:${prepared.gitSha}:${imageDigest}`, diff --git a/tasks/2026-08-03-1729_RUSH_DELIVERY_DEPLOYMENT_ENVIRONMENT_COMPATIBILITY.md b/tasks/2026-08-03-1729_RUSH_DELIVERY_DEPLOYMENT_ENVIRONMENT_COMPATIBILITY.md index b183e2d..729a495 100644 --- a/tasks/2026-08-03-1729_RUSH_DELIVERY_DEPLOYMENT_ENVIRONMENT_COMPATIBILITY.md +++ b/tasks/2026-08-03-1729_RUSH_DELIVERY_DEPLOYMENT_ENVIRONMENT_COMPATIBILITY.md @@ -1,8 +1,9 @@ # Rush Delivery Deployment Environment Compatibility -Status: release candidate for `v0.9.0`; local, clean-checkout, and credentialed -acceptance pass, while merge, tag, release, and remote-tag verification remain -pending. Implementation began after `v0.8.1` was released and verified through +Status: `v0.9.0` was published, but exact-tag consumer smoke found the bounded +GitHub Action transport failed before Dagger execution. The immutable tag is +preserved and corrective patch `v0.9.1` is in progress. Implementation began +after `v0.8.1` was released and verified through [`2026-08-05-0051_HARDEN_OCI_APPLICATION_IMAGES_AND_COMPLETE_PRODUCTION_GUIDES.md`](completed/2026-08-05-0051_HARDEN_OCI_APPLICATION_IMAGES_AND_COMPLETE_PRODUCTION_GUIDES.md). Historical customer-requirement baseline: `BootstrapLaboratory/rush-delivery` @@ -15,7 +16,7 @@ the remote-smoke correction and archives the completed hardening task. All compatibility goldens come from the immutable released tag; implementation starts from the working baseline. -Target release: `v0.9.0`. +Feature release: `v0.9.0`. Corrective release: `v0.9.1`. This task adds two opt-in public metadata contracts and one local-copy import contract: environment-selected application image coordinates, caller-side @@ -357,9 +358,14 @@ Preserve this activation table: - [x] Keep the composite Action on the pinned `dagger/dagger-for-github` `v8.4.1` implementation unless a separately justified dependency update is required. For bounded local copy, make `prepare-workflow.sh` emit the - launcher's generated Dagger Shell script and pass it through the pinned - Action's supported `shell` input; preserve output and trace URL contracts. - Git source mode and `legacy` local copy retain the existing `call` path. + launcher's generated Dagger Shell script. Exact-tag smoke proved that the + pinned Action's `shell` input cannot safely transport the script's quoted + exclusion literals, so `v0.9.1` writes the script to an owner-only file + and invokes the pinned Action's `shell` verb with that file operand. + Preserve output, trace URL, Git source mode, and the `legacy` call path. + Materialize typed host file/directory/socket arguments in the generated + script so file execution cannot rebase runner-temporary paths under the + module workdir. - [x] Implement one shared parser/composer used by the release-asset local launcher and Action wrapper; do not maintain two subtly different pattern engines. @@ -533,7 +539,7 @@ Do not begin this phase while an earlier checkbox or exit gate is incomplete. - [x] Review for unrelated changes, credentials, mutable pins, generated-file mistakes, and changes to `v0.8.1` or older immutable artifacts. -- [ ] Build the local launcher release asset reproducibly, publish its SHA-256 in +- [x] Build the local launcher release asset reproducibly, publish its SHA-256 in the release, and verify the Action-bundled and release-asset implementations are generated from or byte-match the same source. - [x] Generalize the released-consumer smoke workflow before the release @@ -542,10 +548,10 @@ Do not begin this phase while an earlier checkbox or exit gate is incomplete. bounded local launcher, and opt-out compatibility paths. Do not require a post-tag source commit merely to hard-code a SHA that was unknowable in the release candidate. -- [ ] Commit in semantic, reviewable slices; push the implementation branch and +- [x] Commit in semantic, reviewable slices; push the implementation branch and follow the repository's normal review/merge flow. -- [ ] Re-run every release-candidate gate on the exact merged release commit. -- [ ] Create and push annotated tag `v0.9.0` on that commit and publish a GitHub +- [x] Re-run every release-candidate gate on the exact merged release commit. +- [x] Create and push annotated tag `v0.9.0` on that commit and publish a GitHub Release with compatibility, upgrade, examples, limitations, and recovery links. - [ ] Verify Pages, every public `schemas/v0.9.0` URL, the remote Dagger module, @@ -553,6 +559,47 @@ Do not begin this phase while an earlier checkbox or exit gate is incomplete. - [ ] Move this task to `tasks/completed` only after every remote verification passes; commit/push that archive move without retargeting the tag. +`v0.9.0` release evidence: merge commit +`b84f7be11831b47234806dc43dcf1a401034ed74`, annotated tag `v0.9.0`, and +[GitHub Release](https://github.com/BootstrapLaboratory/rush-delivery/releases/tag/v0.9.0). +Exact-merge credentialed acceptance passed in +[run 31058467092](https://github.com/BootstrapLaboratory/rush-delivery/actions/runs/31058467092). +Exact-tag [consumer smoke 31059454313](https://github.com/BootstrapLaboratory/rush-delivery/actions/runs/31059454313) +passed six of eight jobs and exposed the bounded Action failure in both +filesystem-only and provider-off OCI scenarios. The failure was deterministic: +the pinned Action's assembly step rejected quoted Dagger Shell input before the +engine started. The tag and release must never be moved to hide this result. + +## Phase 8: Correct And Release `v0.9.1` + +- [x] Preserve the `v0.9.0` tag, release, schemas, docs snapshot, and launcher + asset; add byte-immutability coverage for the released schemas. +- [x] Reproduce and classify the exact-tag bounded Action failure without + weakening exclusions, quoting, or the shared launcher contract. +- [x] Pass generated Dagger Shell through an owner-only file operand supported by + the pinned Action, retaining its stdout, trace, summary, workdir, flags, and + cloud-token behavior. +- [x] Convert workflow/deploy/release env files, runtime directories, and Docker + sockets to typed host objects before the local-source call; cover both + `--name=value` and `--name value` forms. +- [x] Add focused regression tests and execute a real local bounded workflow from + the generated script file with Action-created env/runtime inputs. +- [x] Freeze Docusaurus documentation from immutable `v0.9.0`, create the + byte-equivalent `schemas/v0.9.1` snapshot, advance current operational + pins/provenance/sites, publish a patch upgrade guide, and update the new + launcher checksum. +- [ ] Run the complete local, clean-checkout, site, lint, Dagger, provider-off, + source-import, toolchain, Cosign/evidence, and credentialed OCI gates on + the patch candidate. +- [ ] Commit/push in reviewable semantic slices, merge through normal review, and + repeat required gates on the exact merged patch commit. +- [ ] Create and push annotated tag `v0.9.1`; publish release notes and the + byte-matched `rush-delivery-local` asset with SHA-256. +- [ ] Verify exact-tag Action bounded/legacy, remote module, downloaded launcher, + Pages, and every public `schemas/v0.9.1` URL before completing this task. +- [ ] Move this task to `tasks/completed` only after every `v0.9.1` remote gate + passes; commit/push the archive move without retargeting either tag. + ## Explicit Non-Goals - [x] Do not add another artifact/provider kind or package-manifest version. @@ -582,5 +629,8 @@ Do not begin this phase while an earlier checkbox or exit gate is incomplete. - [ ] A mixed Node/Python repository deterministically extends the shared Rush toolchain without package bootstrap workarounds or credential exposure. - [ ] Root docs, both sites, schemas, examples, provenance, Action/module pins, - release tag, GitHub Release, and Pages agree on `v0.9.0`. + release tag, GitHub Release, and Pages agree on `v0.9.1`; the immutable + `v0.9.0` archive continues to describe the original release. - [ ] All released `v0.8.1` and older artifacts remain immutable. +- [ ] All released `v0.9.0` artifacts remain immutable, and its failed bounded + Action smoke remains linked as historical release evidence. diff --git a/test/deployment-environment-compatibility-example.test.ts b/test/deployment-environment-compatibility-example.test.ts index 8de93d5..8028a78 100644 --- a/test/deployment-environment-compatibility-example.test.ts +++ b/test/deployment-environment-compatibility-example.test.ts @@ -23,7 +23,7 @@ async function readJson(relativePath: string): Promise { return JSON.parse(await readFile(path.join(repoRoot, relativePath), "utf8")); } -test("v0.9.0 compatibility fragments satisfy current and immutable schemas", async () => { +test("v0.9.1 compatibility fragments satisfy current and immutable schemas", async () => { const ajv = new Ajv2020({ allErrors: true }); for (const [exampleName, schemaName] of [ [ @@ -37,7 +37,7 @@ test("v0.9.0 compatibility fragments satisfy current and immutable schemas", asy ); for (const schemaPath of [ `schemas/${schemaName}`, - `schemas/v0.9.0/${schemaName}`, + `schemas/v0.9.1/${schemaName}`, ]) { const validate = ajv.compile((await readJson(schemaPath)) as AnySchema); assert.equal( @@ -119,7 +119,7 @@ test("documented launcher digest matches the executable bundled by the Action", assert.equal( digest, - "802ed18dc3bce89974d64884fe3c7ca64f3e206faa4c8c8eef237757101bd391", + "35e60214455a84ee27078a0e71481565b1a6d8aab53ba90d511cf0d5970afc27", ); assert.match(localCopyGuide, new RegExp(digest, "u")); assert.match(tutorial, new RegExp(digest, "u")); diff --git a/test/documentation-contract.test.ts b/test/documentation-contract.test.ts index e84e17b..550a1e8 100644 --- a/test/documentation-contract.test.ts +++ b/test/documentation-contract.test.ts @@ -25,8 +25,8 @@ import { parsePackageManifest } from "../src/stages/package-stage/package-manife const testDirectory = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(testDirectory, ".."); -const currentRelease = "v0.9.0"; -const previousRelease = "v0.8.1"; +const currentRelease = "v0.9.1"; +const previousRelease = "v0.9.0"; const daggerVersion = "v0.20.7"; type MarkdownFence = { @@ -425,8 +425,8 @@ test("current release, Dagger, schema, provenance, and tool pins agree", async ( "website-docusaurus/src/pages/index.tsx", ]) { const source = await readRepoFile(homepage); - assert.match(source, /BootstrapLaboratory\/rush-delivery@v0\.9\.0/u); - assert.match(source, /\/schemas\/v0\.9\.0\//u); + assert.match(source, /BootstrapLaboratory\/rush-delivery@v0\.9\.1/u); + assert.match(source, /\/schemas\/v0\.9\.1\//u); } const packageImageSource = await readRepoFile( @@ -434,11 +434,11 @@ test("current release, Dagger, schema, provenance, and tool pins agree", async ( ); assert.match( packageImageSource, - /https:\/\/bootstraplaboratory\.github\.io\/rush-delivery\/build-types\/oci-image\/v0\.9\.0/u, + /https:\/\/bootstraplaboratory\.github\.io\/rush-delivery\/build-types\/oci-image\/v0\.9\.1/u, ); assert.match( packageImageSource, - /https:\/\/github\.com\/BootstrapLaboratory\/rush-delivery@v0\.9\.0/u, + /https:\/\/github\.com\/BootstrapLaboratory\/rush-delivery@v0\.9\.1/u, ); const cosignSource = await readRepoFile("src/application-images/cosign.ts"); diff --git a/test/fixtures/oci-contract/application-image-providers.yaml b/test/fixtures/oci-contract/application-image-providers.yaml index d7274ff..83ca01a 100644 --- a/test/fixtures/oci-contract/application-image-providers.yaml +++ b/test/fixtures/oci-contract/application-image-providers.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/application-image-providers.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/application-image-providers.schema.json providers: release: kind: oci_registry diff --git a/test/fixtures/oci-contract/package-target.yaml b/test/fixtures/oci-contract/package-target.yaml index b6730af..0a8e579 100644 --- a/test/fixtures/oci-contract/package-target.yaml +++ b/test/fixtures/oci-contract/package-target.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/package-target.schema.json +# yaml-language-server: $schema=https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/package-target.schema.json name: control-plane-api artifact: kind: oci_image diff --git a/test/metadata-schemas.test.ts b/test/metadata-schemas.test.ts index cf967b9..aac555f 100644 --- a/test/metadata-schemas.test.ts +++ b/test/metadata-schemas.test.ts @@ -342,7 +342,48 @@ test("released v0.8.1 schema snapshots remain byte-immutable", async () => { } }); -test("current root schemas match the complete v0.9.0 snapshot", async () => { +test("released v0.9.0 schema snapshots remain byte-immutable", async () => { + const expectedDigests: Record = { + "application-image-providers.schema.json": + "47936098454afbfc31bee4dbfd036c2c8fcb2e26ee58663ca3044003dbe20bdf", + "deploy-services-mesh.schema.json": + "10ad5614b4e7a2b743440a82bf3a65191770da2a115a18846716b8b300f30547", + "deploy-target.schema.json": + "bc81dcd4558a24f0f0ee8e959f323a576a1bf3ffc2586dc8ed0970b63ec86663", + "npm-release.schema.json": + "7ab1872347b124510e60b31d581728031566ecf8a1ad816b2557db13355cff10", + "package-manifest.schema.json": + "eceb63524a87cf2d3f88d76c38128423b3d6947c2741280e26142c49be1de757", + "package-target.schema.json": + "6f0f3232a64679161db8119b937941750e2fe855b9506adf30f42fcfac450733", + "rush-cache-providers.schema.json": + "2909539df502c058b22ba2cd71fba3a5e6adffb54b1cbffb8e9db6098ab5f1fd", + "rush-toolchain.schema.json": + "9295e0dc513be186ea9fe7c1e8dd22835171da4b5b8ef49f805e4c07f5c624d2", + "toolchain-image-providers.schema.json": + "3872b6aaf4d607638746f07fafd5e8fd2b9faf5f0586b4fc973323c479aee922", + "validation-target.schema.json": + "7e8f9f914082629a1e0553706b3dced73d5dcd84ac572ff893212876f8ea29d5", + }; + const snapshotNames = (await readdir(path.join(repoRoot, "schemas/v0.9.0"))) + .filter((entry) => entry.endsWith(".schema.json")) + .sort(); + + assert.deepEqual(snapshotNames, Object.keys(expectedDigests).sort()); + + for (const schemaName of snapshotNames) { + const contents = await readFile( + path.join(repoRoot, "schemas/v0.9.0", schemaName), + ); + assert.equal( + createHash("sha256").update(contents).digest("hex"), + expectedDigests[schemaName], + `${schemaName} must remain byte-identical to the released v0.9.0 snapshot`, + ); + } +}); + +test("current root schemas match the complete v0.9.1 snapshot", async () => { const rootNames = ( await readdir(path.join(repoRoot, "schemas"), { withFileTypes: true, @@ -351,7 +392,7 @@ test("current root schemas match the complete v0.9.0 snapshot", async () => { .filter((entry) => entry.isFile() && entry.name.endsWith(".schema.json")) .map((entry) => entry.name) .sort(); - const snapshotNames = (await readdir(path.join(repoRoot, "schemas/v0.9.0"))) + const snapshotNames = (await readdir(path.join(repoRoot, "schemas/v0.9.1"))) .filter((entry) => entry.endsWith(".schema.json")) .sort(); @@ -362,13 +403,13 @@ test("current root schemas match the complete v0.9.0 snapshot", async () => { "utf8", ); const snapshotSchema = await readFile( - path.join(repoRoot, "schemas/v0.9.0", schemaName), + path.join(repoRoot, "schemas/v0.9.1", schemaName), "utf8", ); assert.equal( - snapshotSchema.replace("/schemas/v0.9.0/", "/schemas/"), + snapshotSchema.replace("/schemas/v0.9.1/", "/schemas/"), rootSchema, - `${schemaName} must differ only by the immutable v0.9.0 $id`, + `${schemaName} must differ only by the immutable v0.9.1 $id`, ); } }); diff --git a/test/oci-acceptance-harness.test.ts b/test/oci-acceptance-harness.test.ts index 5ef1bf1..e779e47 100644 --- a/test/oci-acceptance-harness.test.ts +++ b/test/oci-acceptance-harness.test.ts @@ -1563,12 +1563,12 @@ test("OCI acceptance verifier enforces the bundle, image, and Deploy contracts w const provenance = `${JSON.stringify({ buildDefinition: { buildType: - "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.0", + "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.1", resolvedDependencies: [{ digest: { gitCommit: gitSha } }], }, runDetails: { builder: { - id: "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.0", + id: "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.1", }, metadata: { invocationId: `control-plane-api:${gitSha}:${imageDigest}` }, }, diff --git a/test/oci-example.test.ts b/test/oci-example.test.ts index 5894551..bebe46f 100644 --- a/test/oci-example.test.ts +++ b/test/oci-example.test.ts @@ -347,7 +347,7 @@ test("canonical OCI example metadata parses and agrees on one target", async () assert.deepEqual(result.rush_projects, ["control-plane-api"]); }); -test("canonical OCI metadata validates against root and v0.9.0 schemas", async () => { +test("canonical OCI metadata validates against root and v0.9.1 schemas", async () => { const schemaCases = [ { metadataPath: ".dagger/application-images/providers.yaml", @@ -374,7 +374,7 @@ test("canonical OCI metadata validates against root and v0.9.0 schemas", async ( for (const { metadataPath, schemaName } of schemaCases) { const metadata = await readYaml(metadataPath); const source = await readFile(path.join(exampleRoot, metadataPath), "utf8"); - const schemaUrl = `https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/${schemaName}`; + const schemaUrl = `https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/${schemaName}`; assert.match( source, new RegExp( @@ -382,7 +382,7 @@ test("canonical OCI metadata validates against root and v0.9.0 schemas", async ( ), ); - for (const schemaDirectory of ["schemas", "schemas/v0.9.0"]) { + for (const schemaDirectory of ["schemas", "schemas/v0.9.1"]) { const schema = JSON.parse( await readFile( path.join(repositoryRoot, schemaDirectory, schemaName), diff --git a/test/release-smoke-workflow.test.ts b/test/release-smoke-workflow.test.ts index d11e0a4..8c0df66 100644 --- a/test/release-smoke-workflow.test.ts +++ b/test/release-smoke-workflow.test.ts @@ -11,7 +11,7 @@ const workflowPath = path.join( "../.github/workflows/release-smoke.yml", ); -test("release smoke takes an exact ref/commit and covers v0.9.0 consumer paths", async () => { +test("release smoke takes an exact ref/commit and covers v0.9.1 consumer paths", async () => { const source = await readFile(workflowPath, "utf8"); const workflow = parseYaml(source) as { env: Record; @@ -44,7 +44,7 @@ test("release smoke takes an exact ref/commit and covers v0.9.0 consumer paths", RELEASE_SMOKE_REF: "${{ inputs.target_ref }}", }); - assert.match(source, /target_ref:[\s\S]+default: v0\.9\.0/u); + assert.match(source, /target_ref:[\s\S]+default: v0\.9\.1/u); assert.match(source, /expected_commit:[\s\S]+required: true/u); assert.match(source, /ref: \$\{\{ env\.RELEASE_SMOKE_REF \}\}/u); assert.match(source, /uses: \.\/release-source/u); diff --git a/test/scripts/verify-oci-acceptance.mjs b/test/scripts/verify-oci-acceptance.mjs index 9edad19..cdd409e 100644 --- a/test/scripts/verify-oci-acceptance.mjs +++ b/test/scripts/verify-oci-acceptance.mjs @@ -786,12 +786,12 @@ if (!provenance.runDetails?.metadata?.invocationId?.endsWith(artifact.digest)) { } if ( provenance.buildDefinition?.buildType !== - "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.0" || + "https://bootstraplaboratory.github.io/rush-delivery/build-types/oci-image/v0.9.1" || provenance.runDetails?.builder?.id !== - "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.0" + "https://github.com/BootstrapLaboratory/rush-delivery@v0.9.1" ) { throw new Error( - "Acceptance provenance does not identify the v0.9.0 builder contract.", + "Acceptance provenance does not identify the v0.9.1 builder contract.", ); } diff --git a/website-docusaurus/docs-tree.yaml b/website-docusaurus/docs-tree.yaml index 41e478f..1ec9297 100644 --- a/website-docusaurus/docs-tree.yaml +++ b/website-docusaurus/docs-tree.yaml @@ -29,6 +29,10 @@ items: source: ../docs/rush-toolchain.md id: rush-toolchain description: Add digest-pinned, checksummed tools to the shared Rush image. + - title: Upgrade To v0.9.1 + source: ../docs/upgrade-v0.9.1.md + id: upgrade-v0-9-1 + description: Apply the bounded GitHub Action compatibility patch. - title: Upgrade To v0.9.0 source: ../docs/upgrade-v0.9.0.md id: upgrade-v0-9-0 diff --git a/website-docusaurus/docusaurus.config.ts b/website-docusaurus/docusaurus.config.ts index 108f1e2..a41c333 100644 --- a/website-docusaurus/docusaurus.config.ts +++ b/website-docusaurus/docusaurus.config.ts @@ -10,8 +10,9 @@ const baseUrl = process.env.PAGES_BASE_PATH ?? (isProjectPages ? `/${repositoryName}/` : "/"); const url = process.env.PAGES_SITE_URL ?? "https://bootstraplaboratory.github.io"; -const currentDocsVersion = "v0.9.0"; +const currentDocsVersion = "v0.9.1"; const archivedDocsVersions = [ + "v0.9.0", "v0.8.1", "v0.8.0", "v0.7.1", diff --git a/website-docusaurus/scripts/sync-versioned-docs.mjs b/website-docusaurus/scripts/sync-versioned-docs.mjs index 96ad2b2..83dfb61 100644 --- a/website-docusaurus/scripts/sync-versioned-docs.mjs +++ b/website-docusaurus/scripts/sync-versioned-docs.mjs @@ -14,6 +14,7 @@ const githubBlobBase = "https://github.com/BootstrapLaboratory/rush-delivery/blob"; const publishedVersions = [ + "v0.9.0", "v0.8.1", "v0.8.0", "v0.7.1", diff --git a/website-docusaurus/src/pages/index.tsx b/website-docusaurus/src/pages/index.tsx index d14d826..45b36fc 100644 --- a/website-docusaurus/src/pages/index.tsx +++ b/website-docusaurus/src/pages/index.tsx @@ -22,7 +22,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "uses: BootstrapLaboratory/rush-delivery@v0.9.0", + "uses: BootstrapLaboratory/rush-delivery@v0.9.1", "with:", ' dry-run: "false"', " toolchain-image-provider: github", @@ -43,7 +43,7 @@ const examples = [ languageLabel: "sh", highlightLanguage: "bash", code: [ - "dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 call workflow \\", + "dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 call workflow \\", ' --git-sha="${GITHUB_SHA}" \\', ' --event-name="${GITHUB_EVENT_NAME}" \\', " --release-targets-json='[\"npm\"]' \\", @@ -62,7 +62,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "uses: BootstrapLaboratory/rush-delivery@v0.9.0", + "uses: BootstrapLaboratory/rush-delivery@v0.9.1", "with:", " entrypoint: validate", " toolchain-image-provider: github", @@ -79,7 +79,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "# schemas: https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/", + "# schemas: https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/", "", "# .dagger/deploy/services-mesh.yaml", "services:", diff --git a/website/docs-tree.yaml b/website/docs-tree.yaml index 97a91f9..e32b981 100644 --- a/website/docs-tree.yaml +++ b/website/docs-tree.yaml @@ -29,6 +29,10 @@ items: source: ../docs/rush-toolchain.md slug: rush-toolchain description: Add digest-pinned, checksummed tools to the shared Rush image. + - title: Upgrade To v0.9.1 + source: ../docs/upgrade-v0.9.1.md + slug: upgrade-v0-9-1 + description: Apply the bounded GitHub Action compatibility patch. - title: Upgrade To v0.9.0 source: ../docs/upgrade-v0.9.0.md slug: upgrade-v0-9-0 diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index b292f95..5bfada5 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -19,7 +19,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "uses: BootstrapLaboratory/rush-delivery@v0.9.0", + "uses: BootstrapLaboratory/rush-delivery@v0.9.1", "with:", ' dry-run: "false"', " toolchain-image-provider: github", @@ -40,7 +40,7 @@ const examples = [ languageLabel: "sh", highlightLanguage: "shellscript", code: [ - "dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.0 call workflow \\", + "dagger -m github.com/BootstrapLaboratory/rush-delivery@v0.9.1 call workflow \\", ' --git-sha="${GITHUB_SHA}" \\', ' --event-name="${GITHUB_EVENT_NAME}" \\', " --release-targets-json='[\"npm\"]' \\", @@ -59,7 +59,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "uses: BootstrapLaboratory/rush-delivery@v0.9.0", + "uses: BootstrapLaboratory/rush-delivery@v0.9.1", "with:", " entrypoint: validate", " toolchain-image-provider: github", @@ -76,7 +76,7 @@ const examples = [ languageLabel: "yaml", highlightLanguage: "yaml", code: [ - "# schemas: https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.0/", + "# schemas: https://bootstraplaboratory.github.io/rush-delivery/schemas/v0.9.1/", "", "# .dagger/deploy/services-mesh.yaml", "services:", From 4c3b750ad0fb54e4ee655d4c5c69b720b815d2f6 Mon Sep 17 00:00:00 2001 From: Artem Korolev Date: Thu, 6 Aug 2026 00:49:53 +0000 Subject: [PATCH 3/3] test(release): support candidate smoke verification --- .github/workflows/release-smoke.yml | 22 +++++++++++++++++----- test/release-smoke-workflow.test.ts | 12 ++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml index 7df8a62..ba95d8b 100644 --- a/.github/workflows/release-smoke.yml +++ b/.github/workflows/release-smoke.yml @@ -13,6 +13,11 @@ on: description: Expected full peeled commit SHA for target_ref. required: true type: string + verify_release_asset: + description: Download the launcher from the GitHub release instead of the checked-out candidate. + required: true + default: true + type: boolean permissions: contents: read @@ -20,6 +25,7 @@ permissions: env: RELEASE_SMOKE_REF: ${{ inputs.target_ref }} RELEASE_SMOKE_EXPECTED_SHA: ${{ inputs.expected_commit }} + RELEASE_SMOKE_VERIFY_ASSET: ${{ inputs.verify_release_asset }} jobs: released-consumer-smoke: @@ -136,7 +142,7 @@ jobs: > "${result_file}" printf 'RELEASE_SMOKE_RESULT_FILE=%s\n' "${result_file}" >> "${GITHUB_ENV}" - - name: Download and exercise released bounded launcher + - name: Obtain and exercise bounded launcher if: ${{ matrix.surface == 'release-launcher-bounded' }} env: GH_TOKEN: ${{ github.token }} @@ -145,10 +151,16 @@ jobs: set -euo pipefail asset_dir="${RUNNER_TEMP}/rush-delivery-release-asset" mkdir -p "${asset_dir}" - gh release download "${RELEASE_SMOKE_REF}" \ - --repo "${GITHUB_REPOSITORY}" \ - --pattern rush-delivery-local \ - --dir "${asset_dir}" + if [[ ${RELEASE_SMOKE_VERIFY_ASSET} == true ]]; then + gh release download "${RELEASE_SMOKE_REF}" \ + --repo "${GITHUB_REPOSITORY}" \ + --pattern rush-delivery-local \ + --dir "${asset_dir}" + else + cp \ + "${GITHUB_WORKSPACE}/release-source/github-action/rush-delivery-local" \ + "${asset_dir}/rush-delivery-local" + fi cmp \ "${GITHUB_WORKSPACE}/release-source/github-action/rush-delivery-local" \ "${asset_dir}/rush-delivery-local" diff --git a/test/release-smoke-workflow.test.ts b/test/release-smoke-workflow.test.ts index 8c0df66..924bb0f 100644 --- a/test/release-smoke-workflow.test.ts +++ b/test/release-smoke-workflow.test.ts @@ -42,16 +42,28 @@ test("release smoke takes an exact ref/commit and covers v0.9.1 consumer paths", assert.deepEqual(workflow.env, { RELEASE_SMOKE_EXPECTED_SHA: "${{ inputs.expected_commit }}", RELEASE_SMOKE_REF: "${{ inputs.target_ref }}", + RELEASE_SMOKE_VERIFY_ASSET: "${{ inputs.verify_release_asset }}", }); assert.match(source, /target_ref:[\s\S]+default: v0\.9\.1/u); assert.match(source, /expected_commit:[\s\S]+required: true/u); + assert.match( + source, + /verify_release_asset:[\s\S]+default: true[\s\S]+type: boolean/u, + ); assert.match(source, /ref: \$\{\{ env\.RELEASE_SMOKE_REF \}\}/u); assert.match(source, /uses: \.\/release-source/u); assert.match(source, /source-import-policy:/u); assert.match(source, /github-action-legacy/u); assert.match(source, /--source-import-policy=bounded/u); assert.match(source, /gh release download "\$\{RELEASE_SMOKE_REF\}"/u); + assert.ok( + source.includes("if [[ ${RELEASE_SMOKE_VERIFY_ASSET} == true ]]; then"), + ); + assert.match( + source, + /else[\s\S]+cp[\s\S]+release-source\/github-action\/rush-delivery-local/u, + ); assert.match(source, /cmp[\s\S]+github-action\/rush-delivery-local/u); assert.match( source,