2026-07-15 13:10:08 +00:00
#!/usr/bin/env bash
set -euo pipefail
2026-07-15 13:10:09 +00:00
script_dir = $( CDPATH = cd " $( dirname " $0 " ) " && pwd -P)
repo_root = $( CDPATH = cd " $script_dir /.. " && pwd -P)
2026-07-15 13:10:08 +00:00
fixture_version = 1.0.0
2026-07-15 13:10:09 +00:00
for required_command in cargo jq bun rg; do
if ! command -v " $required_command " >/dev/null 2>& 1; then
printf 'version-sync: required command not found: %s\n' " $required_command " >& 2
exit 1
fi
done
2026-07-15 13:10:08 +00:00
current_version = $( bash " $repo_root /scripts/workspace-version.sh " )
2026-07-15 13:10:08 +00:00
cd " $repo_root "
errors = 0
fixture_scan_skipped = 0
report_missing( ) {
file = $1
message = $2
printf '%s:1: error: %s\n' " $file " " $message " >& 2
errors = 1
}
2026-07-15 13:10:08 +00:00
require_regex( ) {
2026-07-15 13:10:08 +00:00
file = $1
2026-07-15 13:10:08 +00:00
pattern = $2
2026-07-15 13:10:08 +00:00
message = $3
2026-07-15 13:10:08 +00:00
if [ [ ! -f " $file " ] ] || ! rg --quiet -- " $pattern " " $file " ; then
2026-07-15 13:10:08 +00:00
report_missing " $file " " $message "
fi
}
2026-07-15 13:10:08 +00:00
require_statement( ) {
2026-07-15 13:10:08 +00:00
file = $1
2026-07-15 13:10:08 +00:00
statement_pattern = $2
2026-07-15 13:10:08 +00:00
message = $3
2026-07-15 13:10:08 +00:00
require_regex " $file " \
" ^[[:space:]]* ${ statement_pattern } [[:space:]]*(#.*)? $" \
" $message "
2026-07-15 13:10:08 +00:00
}
2026-07-15 13:10:08 +00:00
require_single_assignment( ) {
file = $1
variable = $2
assignment_count = $( rg --count-matches \
" ^[[:space:]]* ${ variable } [[:space:]]*= " " $file " || true )
if [ [ " $assignment_count " != 1 ] ] ; then
report_missing " $file " \
" expected exactly one active ${ variable } assignment (found ${ assignment_count :- 0 } ) "
fi
}
workflow_job_block( ) {
job = $1
awk -v job = " $job " '
$0 = = " " job ":" {
in_job = 1
}
in_job && $0 ~ /^ [ 0-9A-Za-z_-] +:$/ && $0 != " " job ":" {
exit
}
in_job {
print
}
' " $release_workflow "
}
require_workflow_job_regex( ) {
job = $1
pattern = $2
message = $3
job_block = $( workflow_job_block " $job " )
if [ [ -z " $job_block " ] ] || ! rg --quiet -- " $pattern " <<< " $job_block " ; then
report_missing " $release_workflow " " $message "
fi
}
validate_macos_version_behavior( ) {
file = $1
interpreter = $2
validation_pattern = $3
if ! rg --quiet -- " $validation_pattern " " $file " ; then
return
fi
validation_status = 0
validation_output = $( env -u OPENPENCIL_VERSION OPENPENCIL_VALIDATE_VERSION_ONLY = 1 \
" $interpreter " " $file " 2>& 1) || validation_status = $?
if [ [ " $validation_status " -ne 0 || " $validation_output " != " $current_version " ] ] ; then
report_missing " $file " \
'validate-only mode without an override must print the canonical version and exit 0'
fi
validation_status = 0
validation_output = $( OPENPENCIL_VERSION = " $current_version " \
OPENPENCIL_VALIDATE_VERSION_ONLY = 1 " $interpreter " " $file " 2>& 1) || \
validation_status = $?
if [ [ " $validation_status " -ne 0 || " $validation_output " != " $current_version " ] ] ; then
report_missing " $file " \
'validate-only mode with a matching override must print the canonical version and exit 0'
fi
mismatch_version = 9.9.9
if [ [ " $current_version " = = " $mismatch_version " ] ] ; then
mismatch_version = 0.0.0
fi
expected_mismatch = " bundle-macos: error: OPENPENCIL_VERSION ( ${ mismatch_version } ) must match Cargo workspace version ( ${ current_version } ) "
validation_status = 0
validation_output = $( OPENPENCIL_VERSION = " $mismatch_version " \
OPENPENCIL_VALIDATE_VERSION_ONLY = 1 " $interpreter " " $file " 2>& 1) || \
validation_status = $?
if [ [ " $validation_status " -ne 1 || " $validation_output " != " $expected_mismatch " ] ] ; then
report_missing " $file " \
'mismatched OPENPENCIL_VERSION must fail validation with actionable error'
fi
}
reject_example_semver_tokens( ) {
file = $1
2026-07-15 13:10:09 +00:00
semver_pattern = '(^|[^0-9A-Za-z.])(v?(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?([+][0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?)([^0-9A-Za-z.]|[.]+([^0-9A-Za-z.]|$)|$)'
2026-07-15 13:10:08 +00:00
rg_status = 0
matches = $( rg --line-number --with-filename --color never -- \
" $semver_pattern " " $file " ) || rg_status = $?
if [ [ " $rg_status " -gt 1 ] ] ; then
printf '%s:1: error: failed to scan version examples (rg status %s)\n' \
" $file " " $rg_status " >& 2
errors = 1
return
fi
if [ [ -n " $matches " ] ] ; then
while IFS = : read -r match_file match_line match_text; do
if [ [ " $match_file " = = scripts/package-windows.nsi && \
" $match_text " = = ' !define VERSION "0.0.0"' ] ] ; then
continue
fi
printf '%s:%s: error: version examples must use X.Y.Z or <version>, not a SemVer release\n' \
" $match_file " " $match_line " >& 2
errors = 1
done <<< " $matches "
fi
}
2026-07-15 13:10:09 +00:00
validate_top_level_readmes( ) {
semver_pattern = '(?<![0-9A-Za-z.])v?(?:0|[1-9][0-9]*)[.](?:0|[1-9][0-9]*)[.](?:0|[1-9][0-9]*)(?:-[0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*)?(?:[+][0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*)?(?![0-9A-Za-z]|[.][0-9A-Za-z])'
readme_files = ( README*.md)
for file in " ${ readme_files [@] } " ; do
rg_status = 0
matches = $( rg --pcre2 --only-matching --line-number --with-filename \
--color never -- " $semver_pattern " " $file " ) || rg_status = $?
if [ [ " $rg_status " -gt 1 ] ] ; then
printf '%s:1: error: failed to scan README version policy (rg status %s)\n' \
" $file " " $rg_status " >& 2
errors = 1
continue
fi
if [ [ -n " $matches " ] ] ; then
while IFS = : read -r match_file match_line token; do
if [ [ " $token " = = v0.7.5 ] ] ; then
continue
fi
printf '%s:%s: error: top-level READMEs must not contain active product SemVer releases; use vX.Y.Z or the workspace-version reader\n' \
" $match_file " " $match_line " >& 2
errors = 1
done <<< " $matches "
fi
done
}
validate_version_sync_ci_readme_paths( ) {
ci_workflow = .github/workflows/version-sync.yml
if [ [ ! -f " $ci_workflow " ] ] ; then
report_missing " $ci_workflow " \
'version-sync CI must run for top-level README changes in pull requests and pushes'
return
fi
read -r pull_request_readmes push_readmes < <(
awk '
/^ pull_request:[ [ :space:] ] *$/ { event = "pull_request" ; next }
/^ push:[ [ :space:] ] *$/ { event = "push" ; next }
/^[ ^[ :space:] ] / { event = "" }
/^[ [ :space:] ] *-[ [ :space:] ] +"README[*][.]md" [ [ :space:] ] *$/ {
if ( event = = "pull_request" ) pull_request_count++
if ( event = = "push" ) push_count++
}
END { print pull_request_count + 0, push_count + 0 }
' " $ci_workflow "
)
if [ [ " $pull_request_readmes " != 1 || " $push_readmes " != 1 ] ] ; then
report_missing " $ci_workflow " \
'version-sync CI must run for top-level README changes in pull requests and pushes'
fi
}
2026-07-15 13:10:08 +00:00
reject_matches( ) {
mode = $1
file = $2
pattern = $3
message = $4
rg_status = 0
if [ [ " $mode " = = fixed ] ] ; then
matches = $( rg --fixed-strings --line-number --with-filename --color never -- \
" $pattern " " $file " ) || rg_status = $?
else
matches = $( rg --line-number --with-filename --color never -- \
" $pattern " " $file " ) || rg_status = $?
fi
if [ [ " $rg_status " -gt 1 ] ] ; then
printf '%s:1: error: failed to scan version policy (rg status %s)\n' \
" $file " " $rg_status " >& 2
errors = 1
return
fi
if [ [ -n " $matches " ] ] ; then
while IFS = : read -r match_file match_line _; do
printf '%s:%s: error: %s\n' " $match_file " " $match_line " " $message " >& 2
done <<< " $matches "
errors = 1
fi
}
2026-07-15 13:10:09 +00:00
validate_manifest_workspace_version( ) {
manifest = $1
relative_manifest = $2
if [ [ ! -f " $manifest " ] ] ; then
report_missing " $relative_manifest " \
'local op-* package manifest returned by cargo metadata does not exist'
return
fi
manifest_result = $( awk '
BEGIN {
in_package = 0
inheritance_count = 0
package_header_line = 1
declaration_line = 0
}
{
line = $0
sub( /\r $/, "" , line)
if ( line ~ /^[ [ :space:] ] *\[ [ ^] ] +\] [ [ :space:] ] *( #.*)?$/) {
header = line
sub( /[ [ :space:] ] *#.*/, "" , header)
gsub( /[ [ :space:] ] /, "" , header)
in_package = ( header = = "[package]" )
if ( in_package && package_header_line = = 1) {
package_header_line = NR
}
next
}
if ( !in_package || line ~ /^[ [ :space:] ] *#/) {
next
}
code = line
sub( /[ [ :space:] ] *#.*/, "" , code)
if ( code ~ /^[ [ :space:] ] *version[ .] workspace[ [ :space:] ] *= [ [ :space:] ] *true[ [ :space:] ] *$/) {
inheritance_count++
if ( declaration_line = = 0) declaration_line = NR
next
}
if ( declaration_line = = 0 &&
code ~ /^[ [ :space:] ] *version( [ .] workspace) ?[ [ :space:] ] *= /) {
declaration_line = NR
}
}
END {
diagnostic_line = declaration_line = = 0 ? package_header_line : declaration_line
printf "%d\t%d\n" , inheritance_count, diagnostic_line
}
' " $manifest " )
IFS = $'\t' read -r inheritance_count diagnostic_line <<< " $manifest_result "
if [ [ " $inheritance_count " != 1 ] ] ; then
printf '%s:%s: error: local op-* package must declare exactly one active version.workspace = true in [package] (found %s)\n' \
" $relative_manifest " " $diagnostic_line " " $inheritance_count " >& 2
errors = 1
fi
}
2026-07-15 13:10:09 +00:00
validate_workspace_package_versions( ) {
if ! metadata = $( cargo metadata --no-deps --format-version 1 --locked) ; then
report_missing Cargo.lock \
'cargo metadata --locked failed; run scripts/sync-version.sh to refresh the lockfile'
return
fi
2026-07-15 13:10:09 +00:00
package_rows_status = 0
package_rows = $( printf '%s\n' " $metadata " | jq -r \
2026-07-15 13:10:09 +00:00
--arg crates_prefix " $repo_root /crates/ " \
2026-07-15 13:10:09 +00:00
'
2026-07-15 13:10:09 +00:00
.packages[ ] ?
| select ( .name | startswith( "op-" ) )
| select ( .manifest_path | startswith( $crates_prefix ) )
| [ .manifest_path, .name, .version]
| @tsv
2026-07-15 13:10:09 +00:00
' 2>& 1) || package_rows_status = $?
if [ [ " $package_rows_status " -ne 0 ] ] ; then
printf '%s\n' " $package_rows " >& 2
2026-07-15 13:10:09 +00:00
report_missing Cargo.toml 'failed to inspect cargo metadata with jq'
return
fi
2026-07-15 13:10:09 +00:00
if [ [ -z " $package_rows " ] ] ; then
report_missing Cargo.toml \
'cargo metadata found no local op-* workspace packages under crates; verify workspace members and repository path resolution'
return
fi
while IFS = $'\t' read -r manifest package package_version; do
2026-07-15 13:10:09 +00:00
relative_manifest = ${ manifest # " $repo_root " / }
validate_manifest_workspace_version " $manifest " " $relative_manifest "
2026-07-15 13:10:09 +00:00
if [ [ " $package_version " != " $current_version " ] ] ; then
2026-07-15 13:10:09 +00:00
printf '%s:1: error: workspace package %s has version %s; expected %s\n' \
" $relative_manifest " " $package " " $package_version " " $current_version " >& 2
errors = 1
2026-07-15 13:10:09 +00:00
fi
done <<< " $package_rows "
2026-07-15 13:10:09 +00:00
}
validate_package_versions( ) {
package_status = 0
package_output = $( cd packages && bun run sync-version:check 2>& 1) || package_status = $?
if [ [ " $package_status " -ne 0 ] ] ; then
printf '%s\n' " $package_output " >& 2
report_missing packages \
'bun run sync-version:check failed; run scripts/sync-version.sh to repair package drift'
fi
}
validate_release_tag( ) {
tag_name =
if [ [ " ${ GITHUB_REF :- } " = = refs/tags/v* ] ] ; then
tag_name = ${ GITHUB_REF #refs/tags/ }
elif [ [ -z " ${ GITHUB_REF :- } " && " ${ GITHUB_REF_NAME :- } " = = v* ] ] ; then
tag_name = $GITHUB_REF_NAME
fi
if [ [ -n " $tag_name " && " ${ tag_name #v } " != " $current_version " ] ] ; then
report_missing environment \
" release tag ${ tag_name } does not match Cargo workspace version ${ current_version } "
fi
}
validate_cli_bundle_version_template( ) {
bundle = crates/op-cli/assets/skill-bundle.json
sentinel = __OPENPENCIL_VERSION__
sentinel_count = $( rg --fixed-strings --count-matches -- " $sentinel " " $bundle " || true )
2026-07-24 13:11:15 +00:00
if [ [ " ${ sentinel_count :- 0 } " != 5 ] ] ; then
2026-07-15 13:10:09 +00:00
report_missing " $bundle " \
2026-07-24 13:11:15 +00:00
" expected exactly 5 version sentinels ${ sentinel } (found ${ sentinel_count :- 0 } ) "
2026-07-15 13:10:09 +00:00
fi
reject_matches fixed " $bundle " " $current_version " \
'embedded CLI bundle must not contain the canonical version literal'
}
validate_rust_product_version_producers( ) {
require_statement crates/op-editor-core/src/state.rs \
'version:[[:space:]]*env!\("CARGO_PKG_VERSION"\)[.]to_owned\(\),' \
'empty documents must derive their version from CARGO_PKG_VERSION'
host_support = crates/op-editor-core/src/host_support.rs
2026-07-15 13:10:09 +00:00
read -r host_production_version_count host_test_version_count < <(
awk \
-v needle = 'src.replace("__OPENPENCIL_VERSION__", env!("CARGO_PKG_VERSION"))' \
'
/^#[ [ :space:] ] *\[ cfg\( test\) \] [ [ :space:] ] *$/ { in_tests = 1 }
index( $0 , needle) {
if ( in_tests) test_count++
else production_count++
}
END { print production_count + 0, test_count + 0 }
' " $host_support "
)
if [ [ " $host_production_version_count " != 2 ] ] ; then
report_missing " $host_support " \
" expected exactly 2 production document templates to derive from CARGO_PKG_VERSION (found ${ host_production_version_count :- 0 } ) "
fi
if [ [ " $host_test_version_count " != 0 ] ] ; then
2026-07-15 13:10:09 +00:00
report_missing " $host_support " \
2026-07-15 13:10:09 +00:00
'ordinary test fixtures must use stable 1.0.0 instead of CARGO_PKG_VERSION'
2026-07-15 13:10:09 +00:00
fi
cli_source = crates/op-cli/src/app_control_cli.rs
require_regex " $cli_source " 'env!\("CARGO_PKG_VERSION"\)' \
'CLI starter documents must derive their version from CARGO_PKG_VERSION'
reject_matches regex " $cli_source " \
'"version"[^[:cntrl:]]*"[0-9]+[.][0-9]+[.][0-9]+' \
'CLI starter documents must not hard-code a product version'
reject_matches regex crates/op-host-desktop/Cargo.toml \
'^[[:space:]]*op-host-native[[:space:]]*=.*path[[:space:]]*=.*version[[:space:]]*=' \
'local op-host-native dependency must not duplicate the product version'
}
validate_workspace_package_versions
validate_package_versions
validate_release_tag
validate_cli_bundle_version_template
validate_rust_product_version_producers
2026-07-15 13:10:09 +00:00
validate_top_level_readmes
validate_version_sync_ci_readme_paths
2026-07-15 13:10:09 +00:00
2026-07-15 13:10:08 +00:00
if [ [ " $current_version " = = " $fixture_version " ] ] ; then
2026-07-15 13:10:09 +00:00
printf 'version-sync: current product version %s equals stable fixture version %s; skipping literal fixture drift scan because stable fixtures and product-version literals are indistinguishable\n' \
2026-07-15 13:10:08 +00:00
" $current_version " " $fixture_version "
2026-07-15 13:10:08 +00:00
fixture_scan_skipped = 1
else
rg_status = 0
matches = $( rg \
--fixed-strings \
--line-number \
--with-filename \
--color never \
--glob '*.rs' \
--glob '!**/op-host-desktop/src/update_check.rs' \
" $current_version " \
crates) || rg_status = $?
2026-07-15 13:10:08 +00:00
2026-07-15 13:10:08 +00:00
if [ [ " $rg_status " -gt 1 ] ] ; then
2026-07-15 13:10:09 +00:00
printf 'version-sync: failed to scan Rust sources with rg (status %s)\n' " $rg_status " >& 2
2026-07-15 13:10:08 +00:00
exit " $rg_status "
fi
2026-07-15 13:10:08 +00:00
2026-07-15 13:10:08 +00:00
if [ [ -n " $matches " ] ] ; then
2026-07-15 13:10:09 +00:00
printf 'version-sync: ordinary Rust fixtures copy current product version %s:\n' \
2026-07-15 13:10:08 +00:00
" $current_version " >& 2
printf '%s\n' " $matches " >& 2
2026-07-15 13:10:09 +00:00
printf 'version-sync: use stable %s test data unless a test explicitly covers compatibility, migration, or updates\n' \
2026-07-15 13:10:08 +00:00
" $fixture_version " >& 2
errors = 1
fi
2026-07-15 13:10:08 +00:00
fi
2026-07-15 13:10:08 +00:00
for macos_script in scripts/bundle-macos.sh tools/bundle-macos.sh; do
2026-07-15 13:10:08 +00:00
require_single_assignment " $macos_script " CANONICAL_VERSION
require_single_assignment " $macos_script " APP_VERSION
2026-07-15 13:10:08 +00:00
if [ [ " $macos_script " = = scripts/bundle-macos.sh ] ] ; then
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'CANONICAL_VERSION[[:space:]]*=[[:space:]]*"\$\("\$WS_ROOT/scripts/workspace-version[.]sh"\)"' \
2026-07-15 13:10:08 +00:00
'macOS packaging must assign CANONICAL_VERSION from scripts/workspace-version.sh'
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'/usr/libexec/PlistBuddy[[:space:]]+-c[[:space:]]+"Set :CFBundleShortVersionString \$APP_VERSION"[[:space:]]+"\$PLIST"' \
2026-07-15 13:10:08 +00:00
'CFBundleShortVersionString must use APP_VERSION'
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'if[[:space:]]+\[\[[[:space:]]*"\$APP_VERSION"[[:space:]]*!=[[:space:]]*"\$CANONICAL_VERSION"[[:space:]]*\]\][[:space:]]*;[[:space:]]*then' \
'OPENPENCIL_VERSION overrides must be rejected when they differ from Cargo'
2026-07-15 13:10:08 +00:00
validation_pattern = '^[[:space:]]*if[[:space:]]+\[\[[[:space:]]*"\$\{OPENPENCIL_VALIDATE_VERSION_ONLY:-\}"[[:space:]]*==[[:space:]]*1[[:space:]]*\]\][[:space:]]*;[[:space:]]*then[[:space:]]*$'
require_regex " $macos_script " " $validation_pattern " \
'macOS packaging must support OPENPENCIL_VALIDATE_VERSION_ONLY immediately after version validation'
validate_macos_version_behavior " $macos_script " bash " $validation_pattern "
2026-07-15 13:10:08 +00:00
else
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'CANONICAL_VERSION[[:space:]]*=[[:space:]]*"\$\("\$ROOT/scripts/workspace-version[.]sh"\)"' \
2026-07-15 13:10:08 +00:00
'macOS packaging must assign CANONICAL_VERSION from scripts/workspace-version.sh'
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'<key>CFBundleShortVersionString</key><string>\$\{APP_VERSION\}</string>' \
2026-07-15 13:10:08 +00:00
'CFBundleShortVersionString must use APP_VERSION'
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'if[[:space:]]+\[[[:space:]]*"\$APP_VERSION"[[:space:]]*!=[[:space:]]*"\$CANONICAL_VERSION"[[:space:]]*\][[:space:]]*;[[:space:]]*then' \
'OPENPENCIL_VERSION overrides must be rejected when they differ from Cargo'
2026-07-15 13:10:08 +00:00
validation_pattern = '^[[:space:]]*if[[:space:]]+\[[[:space:]]*"\$\{OPENPENCIL_VALIDATE_VERSION_ONLY:-\}"[[:space:]]*=[[:space:]]*1[[:space:]]*\][[:space:]]*;[[:space:]]*then[[:space:]]*$'
require_regex " $macos_script " " $validation_pattern " \
'macOS packaging must support OPENPENCIL_VALIDATE_VERSION_ONLY immediately after version validation'
validate_macos_version_behavior " $macos_script " sh " $validation_pattern "
2026-07-15 13:10:08 +00:00
fi
2026-07-15 13:10:08 +00:00
require_statement " $macos_script " \
'APP_VERSION[[:space:]]*=[[:space:]]*"\$\{OPENPENCIL_VERSION:-\$CANONICAL_VERSION\}"' \
2026-07-15 13:10:08 +00:00
'OPENPENCIL_VERSION must default to the Cargo workspace version'
reject_matches regex " $macos_script " \
'OPENPENCIL_VERSION:-[0-9]+[.][0-9]+[.][0-9]+' \
'OPENPENCIL_VERSION must fall back to the Cargo workspace version'
reject_matches regex " $macos_script " \
'CFBundleShortVersionString[^[:cntrl:]]*[0-9]+[.][0-9]+[.][0-9]+' \
'CFBundleShortVersionString must use the resolved Cargo workspace version'
done
2026-07-15 13:10:08 +00:00
require_regex scripts/package-windows.nsi \
'^[[:space:]]*;[[:space:]]*makensis[[:space:]]+"/DVERSION=X[.]Y[.]Z"' \
'NSIS compile example must use /DVERSION=X.Y.Z'
require_regex scripts/install-op.sh \
'^[[:space:]]*#[[:space:]]*OP_VERSION=(X[.]Y[.]Z|<version>)[[:space:]]+[.]/install-op[.]sh' \
'install usage example must use OP_VERSION=X.Y.Z or OP_VERSION=<version>'
reject_example_semver_tokens scripts/package-windows.nsi
reject_example_semver_tokens scripts/install-op.sh
2026-07-15 13:10:08 +00:00
release_workflow = .github/workflows/rust-release.yml
2026-07-15 13:10:08 +00:00
require_workflow_job_regex version \
'^[[:space:]]*-[[:space:]]+uses:[[:space:]]+actions/checkout@v4[[:space:]]*$' \
'version preflight must check out the repository'
require_workflow_job_regex version \
'^[[:space:]]*version:[[:space:]]*\$\{\{[[:space:]]*steps[.]version[.]outputs[.]version[[:space:]]*\}\}[[:space:]]*$' \
'version preflight must expose the canonical version as a job output'
require_workflow_job_regex version \
'^[[:space:]]*(-[[:space:]]+)?id:[[:space:]]+version[[:space:]]*$' \
'version preflight must identify the canonical version step'
require_workflow_job_regex version \
'^[[:space:]]*cargo_version[[:space:]]*=[[:space:]]*"\$\(scripts/workspace-version[.]sh\)"[[:space:]]*$' \
2026-07-15 13:10:08 +00:00
'release version computation must invoke scripts/workspace-version.sh'
2026-07-15 13:10:08 +00:00
require_workflow_job_regex version \
'^[[:space:]]*tag_version[[:space:]]*=[[:space:]]*"\$\{GITHUB_REF_NAME#v\}"[[:space:]]*$' \
2026-07-15 13:10:08 +00:00
'release version computation must derive the version from v* tags'
2026-07-15 13:10:08 +00:00
require_workflow_job_regex version \
'^[[:space:]]*if[[:space:]]+\[\[[[:space:]]*"\$tag_version"[[:space:]]*!=[[:space:]]*"\$cargo_version"[[:space:]]*\]\][[:space:]]*;[[:space:]]*then[[:space:]]*$' \
2026-07-15 13:10:08 +00:00
'release tags must be compared with the Cargo workspace version'
2026-07-15 13:10:08 +00:00
require_workflow_job_regex version \
'^[[:space:]]*echo[[:space:]]+"version=\$cargo_version"[[:space:]]*>>[[:space:]]*"\$GITHUB_OUTPUT"[[:space:]]*$' \
'version preflight must write the canonical version to GITHUB_OUTPUT'
require_single_assignment " $release_workflow " cargo_version
require_single_assignment " $release_workflow " tag_version
require_workflow_job_regex build \
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
'build must depend on the version preflight job'
require_workflow_job_regex web-docker \
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
'web-docker must depend on the version preflight job'
require_workflow_job_regex sdk-packages \
'^[[:space:]]*needs:[[:space:]]*version[[:space:]]*$' \
'sdk-packages must depend on the version preflight job'
require_workflow_job_regex release-draft \
2026-07-20 13:32:22 +00:00
'^[[:space:]]*needs:[[:space:]]*\[version,[[:space:]]*build,[[:space:]]*web-docker,[[:space:]]*sdk-packages,[[:space:]]*vsix\][[:space:]]*$' \
2026-07-15 13:10:08 +00:00
'release-draft must preserve artifact dependencies and depend on version preflight'
require_workflow_job_regex package-managers \
'^[[:space:]]*needs:[[:space:]]*\[version,[[:space:]]*release-draft\][[:space:]]*$' \
'package-managers must preserve release dependency and depend on version preflight'
for version_consumer in build web-docker sdk-packages release-draft package-managers; do
require_workflow_job_regex " $version_consumer " \
'needs[.]version[.]outputs[.]version' \
" ${ version_consumer } must consume the canonical version job output "
done
require_workflow_job_regex sdk-packages \
'bun[[:space:]]+run[[:space:]]+sync-version:check' \
'sdk-packages must verify package versions after installing dependencies'
reject_matches regex " $release_workflow " \
'^[[:space:]]*version[[:space:]]*=.*GITHUB_REF_NAME#v' \
'publish paths must consume the canonical version job output'
reject_matches regex " $release_workflow " \
'^[[:space:]]*tag[[:space:]]*=[[:space:]]*"\$GITHUB_REF_NAME"' \
'publish paths must not begin an independent two-step tag derivation'
reject_matches regex " $release_workflow " \
'^[[:space:]]*version[[:space:]]*=[[:space:]]*"\$\{tag#v\}"' \
'publish paths must consume the canonical version job output'
2026-07-15 13:10:08 +00:00
reject_matches fixed " $release_workflow " 'echo "OP_VERSION=${GITHUB_REF_NAME#v}"' \
'OP_VERSION must not be written directly from the release tag'
reject_matches fixed " $release_workflow " 'echo "OP_VERSION=${ver:-0.0.0}"' \
'OP_VERSION must not use an independent manifest parser or fallback'
if [ [ " $errors " -ne 0 ] ] ; then
2026-07-15 13:10:08 +00:00
exit 1
fi
2026-07-15 13:10:08 +00:00
if [ [ " $fixture_scan_skipped " -eq 0 ] ] ; then
2026-07-15 13:10:09 +00:00
printf 'version-sync: no ordinary Rust fixtures copy current product version %s\n' \
2026-07-15 13:10:08 +00:00
" $current_version "
fi
2026-07-15 13:10:09 +00:00
printf 'version-sync: all managed versions derive from Cargo workspace version %s\n' \
2026-07-15 13:10:08 +00:00
" $current_version "