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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 274 additions & 82 deletions .github/scripts/publish-abi-staging-request.sh

Large diffs are not rendered by default.

260 changes: 182 additions & 78 deletions .github/scripts/test-publish-abi-staging-request.sh

Large diffs are not rendered by default.

38 changes: 25 additions & 13 deletions .github/workflows/abi-staging-request-feed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ jobs:
actual_tree=$(git -C "$head_root" rev-parse 'HEAD^{tree}')
[[ $actual_head == "$head" ]]
env -u GH_TOKEN -u GITHUB_TOKEN git -C "$head_root" fetch --no-tags origin "$PROTECTED_SHA"
bash authority/scripts/fetch-exact-musl-gitlink.sh \
--source-root "$head_root" \
--commit "$head"

target_abi=$(sed -nE 's/^pub const ABI_VERSION: u32 = ([0-9]+);$/\1/p' \
"$head_root/crates/shared/src/lib.rs")
Expand All @@ -185,25 +188,34 @@ jobs:
check_log="$evidence/reports/$pr-check.log"
set +e
(
set -euo pipefail
cd "$head_root"
candidate_home="$evidence/candidate-homes/$pr"
mkdir -p "$candidate_home/tmp"
chmod 700 "$candidate_home" "$candidate_home/tmp"
nix_bin=$(command -v nix)
bash_bin=$(command -v bash)
env -u GH_TOKEN -u GITHUB_TOKEN -u ACTIONS_RUNTIME_TOKEN \
-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \
env -i \
HOME="$candidate_home" \
TMPDIR="$candidate_home/tmp" \
CI=true \
LOGNAME=kandelo \
USER=kandelo \
KANDELO_NIX_BIN="$nix_bin" \
PATH=/usr/bin:/bin \
"$bash_bin" scripts/dev-shell.sh \
env ABI_CHECK_BASE_REF="$PROTECTED_SHA" \
bash scripts/check-abi-version.sh
candidate_env=(
env -u GH_TOKEN -u GITHUB_TOKEN -u ACTIONS_RUNTIME_TOKEN
-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL
env -i
HOME="$candidate_home"
TMPDIR="$candidate_home/tmp"
CI=true
LOGNAME=kandelo
USER=kandelo
KANDELO_NIX_BIN="$nix_bin"
PATH=/usr/bin:/bin
)
# The ABI snapshot build resolves both SDK architectures. A clean
# exact head has neither sysroot, so prepare both before checking.
"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \
bash scripts/build-musl.sh 2>&1 | tail -c 131072
"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \
bash scripts/build-musl.sh --arch wasm64posix 2>&1 | tail -c 131072
"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \
env ABI_CHECK_BASE_REF="$PROTECTED_SHA" \
bash scripts/check-abi-version.sh
) 2>&1 | head -c 1048576 >"$check_log"
check_status=${PIPESTATUS[0]}
set -e
Expand Down
2 changes: 1 addition & 1 deletion abi/staging/request-policy.generated.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions abi/staging/request-policy.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema = 1
kind = "kandelo-abi-staging-request-policy"
version = 13
version = 15
issuer_repository = "Automattic/kandelo"
issuer_workflow = ".github/workflows/abi-staging-request-feed.yml"
automatic_same_repository = true
Expand All @@ -12,9 +12,11 @@ max_evidence_bindings = 512

addressed_taps = ["kandelo-dev/homebrew-tap-core"]

# This is the approved MVP implementation identity. Publication remains
# observe-only until protected hosted canaries have supplied activation evidence.
# This is the protected request derivation, publication, and validation
# implementation identity. Active publication remains non-endorsing; candidate
# execution, verification, admission, and promotion have separate authorities.
implementation_paths = [
".github/scripts/publish-abi-staging-request.sh",
".github/scripts/update-abi-staging-check.sh",
".github/workflows/abi-staging-merge-gate.yml",
".github/workflows/abi-staging-pages-canary.yml",
Expand Down Expand Up @@ -96,6 +98,7 @@ implementation_paths = [
"tools/xtask/src/abi_staging/product_manifest.rs",
"tools/xtask/src/abi_staging/records.rs",
"tools/xtask/src/abi_staging/request_derivation.rs",
"tools/xtask/src/abi_staging/request_feed.rs",
"tools/xtask/src/abi_staging/request_policy.rs",
"tools/xtask/src/abi_staging/selection.rs",
]
13 changes: 9 additions & 4 deletions docs/repository-organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,15 @@ validation and a deterministic local transition miniature. The protected
request workflow is owned by
`.github/workflows/abi-staging-request-feed.yml`; its source policy and
activation are owned by `abi/staging/request-policy.toml` and
`abi/staging/request-feed-activation.toml`. Active mode publishes an exact,
append-only, non-endorsing request Release asset for protected tap-side
reconciliation. The companion tap workflow remains observe-only and
read-only.
`abi/staging/request-feed-activation.toml`. Active mode publishes each exact,
non-endorsing request in its own content-addressed prerelease for protected
tap-side reconciliation. The publisher uploads only while the prerelease is a
draft, then makes it public and requires GitHub immutable-release protection
plus an anonymous byte-for-byte readback before reporting success. Historical
requests remain separate immutable records; a later head or policy creates a
new prerelease instead of appending to an existing public release. The
companion tap workflow remains read-only until its separately reviewed
activation changes.

Candidate execution and publication, enforced GitHub Check updates,
promotion, protected ABI-history mutation, and production Pages deployment
Expand Down
79 changes: 79 additions & 0 deletions scripts/check-abi-staging-request-workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,36 @@ def check_workflow(workflow)
classify_source.include?("env -u GH_TOKEN -u GITHUB_TOKEN") &&
classify_source.include?("env -i") &&
classify_source.include?('HOME="$candidate_home"') &&
classify_source.include?("authority/scripts/fetch-exact-musl-gitlink.sh") &&
classify_source.include?('--source-root "$head_root"') &&
classify_source.include?('--commit "$head"') &&
classify_source.include?("bash scripts/build-musl.sh") &&
classify_source.include?("bash scripts/build-musl.sh --arch wasm64posix") &&
classify_source.scan("tail -c 131072").length == 2 &&
classify_source.include?('env ABI_CHECK_BASE_REF="$PROTECTED_SHA"') &&
classify_source.include?("head -c 1048576") &&
classify_source.include?("scripts/check-abi-version.sh") &&
classify_source.include?("structural-report.json") &&
classify_source.include?("HEAD^{tree}"),
"classification does not bind and check each uncredentialed exact head")
wasm32_build = classify_source.index("bash scripts/build-musl.sh")
wasm64_build = classify_source.index("bash scripts/build-musl.sh --arch wasm64posix")
candidate_prefix = '"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh'
fail_closed_sequence = <<~'SH'.strip
check_log="$evidence/reports/$pr-check.log"
set +e
(
set -euo pipefail
cd "$head_root"
SH
check(classify_source.include?(fail_closed_sequence) &&
classify_source.scan('"$bash_bin" scripts/dev-shell.sh').length == 3 &&
classify_source.scan(candidate_prefix).length == 3 &&
!classify_source.include?("git submodule") &&
classify_source.index("fetch-exact-musl-gitlink.sh") < wasm32_build &&
wasm32_build < wasm64_build &&
wasm64_build < classify_source.index("bash scripts/check-abi-version.sh"),
"classification does not prepare the exact sysroot in one fail-closed stripped environment")
check(!classify_source.match?(/gh\s+(?:release|api\s+--method)/),
"classification may not write through GitHub")
check(!classify.fetch("env", {}).key?("GH_TOKEN"),
Expand Down Expand Up @@ -279,6 +303,61 @@ def rejected_mutation(workflow, label)
step = copy.dig("jobs", "derive-request", "steps").find { |item| item["run"]&.include?("structural-report validate") }
step["run"] = step.fetch("run").gsub("structural-report validate", "echo trust-report")
},
"missing classification musl materialization" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("fetch-exact-musl-gitlink.sh")
end
step["run"] = step.fetch("run").gsub(
/^\s*bash authority\/scripts\/fetch-exact-musl-gitlink\.sh.*?^\s*--commit "\$head"\n/m,
""
)
},
"missing classification sysroot build" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("bash scripts/build-musl.sh")
end
step["run"] = step.fetch("run").gsub(
"bash scripts/build-musl.sh 2>&1",
"true # ABI sysroot build removed"
)
},
"missing classification wasm64 sysroot build" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("bash scripts/build-musl.sh --arch wasm64posix")
end
step["run"] = step.fetch("run").gsub(
"bash scripts/build-musl.sh --arch wasm64posix",
"true # wasm64 ABI sysroot build removed"
)
},
"classification sysroot failure can continue" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("candidate_env=(")
end
step["run"] = step.fetch("run").sub(
" set +e\n (\n set -euo pipefail\n",
" set +e\n (\n"
)
},
"classification wasm32 build escapes stripped environment" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("candidate_env=(")
end
step["run"] = step.fetch("run").sub(
'"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh',
'env "$bash_bin" scripts/dev-shell.sh'
)
},
"classification ABI check escapes stripped environment" => lambda { |copy|
step = copy.dig("jobs", "classify-exact-head", "steps").find do |item|
item["run"]&.include?("candidate_env=(")
end
prefix = '"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh'
source = step.fetch("run")
position = source.rindex(prefix)
step["run"] = source.dup
step["run"][position, prefix.length] = 'env "$bash_bin" scripts/dev-shell.sh'
},
"missing exact musl materialization" => lambda { |copy|
step = copy.dig("jobs", "derive-request", "steps").find do |item|
item["run"]&.include?("fetch-exact-musl-gitlink.sh")
Expand Down
11 changes: 6 additions & 5 deletions tools/xtask/src/abi_staging/check_projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::abi_staging::consumer_registry::ApplicabilityV1;
use crate::abi_staging::guard_registry::GuardCodeV1;
use crate::abi_staging::product_manifest::VfsArchitectureV1;
use crate::abi_staging::product_manifest::{atomic_write_regular, read_bounded_regular_file};
use crate::abi_staging::request_feed::request_asset_url;
use crate::abi_staging::records::{
AbiStagingRecordV1, AbiStagingRequestV1, PromotionStateV1, RecordCommonV1, RetryNextActionV1,
SubjectKindV1, TerminalOutcomeV1, WorkStateV1, request_is_current, validate_record,
Expand Down Expand Up @@ -593,6 +594,7 @@ fn validate_input(input: &CurrentCheckProjectionInputV1) -> Result<(), String> {
validate_request_reference(
&request_source.immutable_reference,
&input.context.repository,
input.context.pull_request_number,
&input.context.exact_head,
&request_source.digest,
)?;
Expand Down Expand Up @@ -1150,14 +1152,13 @@ fn validate_record_link(link: &RecordLinkV1) -> Result<(), String> {
fn validate_request_reference(
reference: &str,
repository: &str,
pull_request: u64,
head: &str,
digest: &str,
) -> Result<(), String> {
let filename = format!("candidate-request-{head}-sha256-{digest}.json");
let prefix = format!("https://github.com/{repository}/releases/download/");
let expected = request_asset_url(repository, pull_request, head, digest);
if reference.len() > MAX_REFERENCE_BYTES
|| !reference.starts_with(&prefix)
|| !reference.ends_with(&filename)
|| reference != expected
|| reference.contains('?')
|| reference.contains('#')
|| reference
Expand Down Expand Up @@ -1743,7 +1744,7 @@ mod tests {
request: Some(CurrentRequestProjectionV1 {
digest: request_digest.clone(),
immutable_reference: format!(
"https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19/candidate-request-{HEAD}-sha256-{request_digest}.json"
"https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19-sha256-{request_digest}/candidate-request-{HEAD}-sha256-{request_digest}.json"
),
request,
}),
Expand Down
Loading
Loading