Files
lserp_cs_6.0/插件库/Lskj.AgentBridge/Deployment/Build-CommercialPackage.sh
2026-08-14 14:28:28 +08:00

720 lines
28 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
repo_root="${1:-$(pwd)}"
output_root="${2:-$repo_root/artifacts}"
package_version="${LSERP_PACKAGE_VERSION:-0.4.0}"
if [[ ! "$package_version" =~ ^[0-9]{1,4}\.[0-9]{1,4}\.[0-9]{1,4}$ ]]; then
echo 'LSERP_PACKAGE_VERSION must be a three-part numeric version.' >&2
exit 3
fi
dotnet6="${LSERP_DOTNET6:-dotnet}"
dotnet8="${LSERP_DOTNET8:-dotnet}"
python_bin="${LSERP_PYTHON:-python3}"
pwsh_bin="${LSERP_PWSH:-pwsh}"
npm_bin="${LSERP_NPM:-npm}"
astrbot_contract_python="${LSERP_ASTRBOT_CONTRACT_PYTHON:-}"
astrbot_source="${LSERP_ASTRBOT_SOURCE:-}"
host_certificate_thumbprint="${LSERP_HOST_CERT_THUMBPRINT:-}"
host_timestamp_url="${LSERP_HOST_TIMESTAMP_URL:-}"
host_certificate_store="${LSERP_HOST_CERT_STORE:-CurrentUser}"
host_signtool_path="${LSERP_HOST_SIGNTOOL:-}"
host_signing_requested=false
if [[ -z "$astrbot_contract_python" || ! -f "$astrbot_contract_python" || \
-z "$astrbot_source" || ! -d "$astrbot_source" ]]; then
echo 'Commercial packaging requires LSERP_ASTRBOT_CONTRACT_PYTHON and LSERP_ASTRBOT_SOURCE for the reviewed AstrBot 4.27.2 checkout.' >&2
exit 3
fi
if [[ -n "$host_certificate_thumbprint" || -n "$host_timestamp_url" || \
-n "$host_signtool_path" ]]; then
if [[ ! "$host_certificate_thumbprint" =~ ^[A-Fa-f0-9]{40}$ || \
! "$host_timestamp_url" =~ ^https:// || \
( "$host_certificate_store" != CurrentUser && \
"$host_certificate_store" != LocalMachine ) ]]; then
echo 'Host signing parameters are incomplete or invalid.' >&2
exit 3
fi
host_signing_requested=true
fi
repo_root="$(cd "$repo_root" && pwd)"
mkdir -p "$output_root"
output_root="$(cd "$output_root" && pwd)"
package_name="Lserp-AgentPet-${package_version}-win-x64"
final_directory="$output_root/$package_name"
final_archive="$output_root/$package_name.zip"
if [[ -e "$final_directory" || -e "$final_archive" ]]; then
echo "Refusing to overwrite an existing package: $package_name" >&2
exit 2
fi
temporary_root="$(mktemp -d "${TMPDIR:-/tmp}/lserp-commercial-package.XXXXXX")"
cleanup() {
rm -rf -- "$temporary_root"
}
trap cleanup EXIT
logs="$temporary_root/logs"
stage="$temporary_root/$package_name"
mkdir -p "$logs" "$stage/Host" "$stage/AstrBotPlugin" \
"$stage/Deployment/SqlServer" "$stage/Deployment/customer-profiles" \
"$stage/PythonWheels" "$stage/Contracts"
plugin_root="$repo_root/插件库/astrbot_plugin_lserp"
python_test_venv="$temporary_root/python-test-venv"
python_test_wheels="$temporary_root/python-test-wheels"
mkdir -p "$python_test_wheels"
"$python_bin" -m pip download \
--only-binary=:all: \
--require-hashes \
--dest "$python_test_wheels" \
--requirement "$plugin_root/requirements.txt"
"$python_bin" -m pip download \
--only-binary=:all: \
--platform win_amd64 \
--require-hashes \
--dest "$stage/PythonWheels" \
--requirement "$plugin_root/requirements.txt"
"$python_bin" -m venv "$python_test_venv"
if [[ -f "$python_test_venv/bin/python" ]]; then
test_python="$python_test_venv/bin/python"
elif [[ -f "$python_test_venv/Scripts/python.exe" ]]; then
test_python="$python_test_venv/Scripts/python.exe"
else
echo 'Isolated Python test runtime was not created.' >&2
exit 3
fi
"$test_python" -m pip install \
--no-index \
--require-hashes \
--find-links "$python_test_wheels" \
--requirement "$plugin_root/requirements.txt"
"$test_python" -m pip check
astrbot_contract_evidence="$logs/astrbot-runtime-contract.json"
"$astrbot_contract_python" "$plugin_root/verify_astrbot_contract.py" \
--astrbot-source "$astrbot_source" \
--output "$astrbot_contract_evidence" \
2>&1 | tee "$logs/astrbot-runtime-contract.log"
if [[ ! -s "$astrbot_contract_evidence" ]]; then
echo 'Actual AstrBot runtime contract evidence was not created.' >&2
exit 4
fi
"$dotnet6" run --project "$repo_root/插件库/Lskj.CommandKernel.Tests/Lskj.CommandKernel.Tests.csproj" -c Release 2>&1 | tee "$logs/kernel.log"
"$dotnet8" build \
"$repo_root/插件库/Lskj.LegacyApiCompatibility.Tests/Lskj.LegacyApiCompatibility.Tests.csproj" \
-c Release 2>&1 | tee "$logs/legacy-api.log"
"$dotnet8" run \
--project "$repo_root/插件库/Lskj.SqlContract.Tests/Lskj.SqlContract.Tests.csproj" \
-c Release -- "$repo_root" 2>&1 | tee "$logs/sql-contract.log"
"$dotnet8" run --project "$repo_root/插件库/Lskj.AgentPet.Host.Tests/Lskj.AgentPet.Host.Tests.csproj" -c Release 2>&1 | tee "$logs/host.log"
(cd "$repo_root/插件库/Lskj.AgentPet" && "$npm_bin" test) 2>&1 | tee "$logs/node.log"
(cd "$plugin_root" && "$test_python" -m unittest discover -s tests -v) 2>&1 | tee "$logs/python.log"
if grep -Eq 'skipped=[1-9][0-9]*' "$logs/python.log"; then
echo 'Commercial AstrBot verification must not skip worker tests.' >&2
exit 4
fi
"$pwsh_bin" -NoLogo -NoProfile -File \
"$repo_root/插件库/Lskj.AgentBridge/Deployment/CommercialPackage/Test-DeploymentContracts.ps1" \
-RepoRoot "$repo_root" 2>&1 | tee "$logs/deployment.log"
host_publish="$temporary_root/host-publish"
"$dotnet8" publish \
"$repo_root/插件库/Lskj.AgentPet.Host/Lskj.AgentPet.Host.csproj" \
-c Release \
-p:PublishProfile=WinX64 \
--output "$host_publish" 2>&1 | tee "$logs/publish.log"
if [[ ! -s "$host_publish/Lskj.AgentPet.Host.exe" ]]; then
echo 'Windows host publish output is missing.' >&2
exit 3
fi
bridge_cli_publish="$temporary_root/bridge-cli-publish"
"$dotnet8" publish \
"$repo_root/插件库/Lskj.BridgeCli/Lskj.BridgeCli.csproj" \
-c Release \
-r win-x64 \
--self-contained true \
-p:Version="$package_version" \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:EnableCompressionInSingleFile=true \
-p:PublishTrimmed=false \
-p:PublishReadyToRun=false \
-p:DebugType=None \
-p:DebugSymbols=false \
--output "$bridge_cli_publish" 2>&1 | tee "$logs/bridge-cli-publish.log"
if [[ ! -s "$bridge_cli_publish/lserp-agent-cli.exe" ]] || \
find "$bridge_cli_publish" -mindepth 1 -maxdepth 1 \
! -name 'lserp-agent-cli.exe' | grep -q .; then
echo 'Windows bridge-only CLI must be one self-contained executable.' >&2
exit 3
fi
if [[ -e "$host_publish/lserp-agent-cli.exe" ]]; then
echo 'Host publish unexpectedly contains the bridge CLI name.' >&2
exit 3
fi
cp "$bridge_cli_publish/lserp-agent-cli.exe" "$host_publish/"
if [[ ! -s "$host_publish/lserp-agent-cli.exe" ]]; then
echo 'Merged commercial bridge-only CLI is missing.' >&2
exit 3
fi
host_authenticode_signed=false
if [[ "$host_signing_requested" == true ]]; then
sign_arguments=(
-NoLogo -NoProfile -File
"$repo_root/插件库/Lskj.AgentBridge/Deployment/Sign-LserpAgentPetHost.ps1"
-HostDirectory "$host_publish"
-CertificateThumbprint "$host_certificate_thumbprint"
-CertificateStoreLocation "$host_certificate_store"
-TimestampUrl "$host_timestamp_url"
)
if [[ -n "$host_signtool_path" ]]; then
sign_arguments+=( -SignToolPath "$host_signtool_path" )
fi
"$pwsh_bin" "${sign_arguments[@]}" 2>&1 | tee "$logs/host-signing.log"
host_authenticode_signed=true
fi
if [[ -e "$host_publish/Web" ]] || \
find "$host_publish" -maxdepth 1 -type f \( \
-name 'index.html' -o -name 'pet.css' -o -name 'pet-runtime.js' \
-o -name 'bridge-client.js' -o -name 'pet-shell.js' \) | grep -q .; then
echo 'Commercial host publish must not contain mutable external Web assets.' >&2
exit 3
fi
cp -R "$host_publish/." "$stage/Host/"
astrbot_plugin_files=(
README.md
__init__.py
_conf_schema.json
astrbot-contract.json
astrbot_contract.py
attachment_extract.py
attachment_provenance.py
attachment_sandbox.py
attachment_worker.py
bridge_protocol.py
main.py
metadata.yaml
pdf_render_sandbox.py
pdf_render_worker.py
pdf_vision.py
plan_chain.py
prompt.py
purchase_tabular_binding.py
purchase_vision_binding.py
requirements.txt
session_auth.py
tools.py
verify_astrbot_contract.py
verify_minimax_vlm_contract.py
vision.py
)
for relative in "${astrbot_plugin_files[@]}"; do
source="$plugin_root/$relative"
if [[ ! -f "$source" || -L "$source" ]]; then
echo "Required AstrBot plugin source is missing or linked: $relative" >&2
exit 3
fi
cp "$source" "$stage/AstrBotPlugin/$relative"
done
deployment_root="$repo_root/插件库/Lskj.AgentBridge/Deployment"
cp "$deployment_root/CUSTOMER_ACCEPTANCE.md" "$stage/Deployment/"
cp "$deployment_root/WRITE_ACCEPTANCE.md" "$stage/Deployment/"
cp "$deployment_root/DYNAMIC_MODULE_WRITE_ACCEPTANCE.md" "$stage/Deployment/"
cp "$deployment_root/FIELD_VALIDATION_RUNBOOK.md" "$stage/Deployment/"
cp "$deployment_root/THIRD_PARTY_COMPLIANCE.md" "$stage/Deployment/"
cp "$deployment_root/guga-upstream-audit.v1.json" "$stage/Deployment/"
cp "$deployment_root/New-WorkflowAcceptanceEvidence.ps1" "$stage/Deployment/"
cp "$deployment_root/New-DynamicModuleWriteAcceptance.ps1" "$stage/Deployment/"
cp "$deployment_root/dynamic-module-write-modules.example.json" "$stage/Deployment/"
cp "$deployment_root/New-DynamicModuleUpdateAcceptance.ps1" "$stage/Deployment/"
cp "$deployment_root/dynamic-module-update-modules.example.json" "$stage/Deployment/"
cp "$deployment_root/New-WorkflowWriteCasesTemplate.ps1" "$stage/Deployment/"
cp "$deployment_root/New-WorkflowWriteIntegrationEvidence.ps1" "$stage/Deployment/"
cp "$deployment_root/Invoke-WorkflowWriteCaseCapture.ps1" "$stage/Deployment/"
cp "$deployment_root/Invoke-LserpFieldReadOnlyValidation.ps1" "$stage/Deployment/"
cp "$deployment_root/field-readonly-validation.example.json" "$stage/Deployment/"
cp "$deployment_root/Invoke-LserpReadOnlySessionPreflight.ps1" "$stage/Deployment/"
cp "$deployment_root/Invoke-LserpSelectOnlyCatalogSnapshot.ps1" "$stage/Deployment/"
cp "$deployment_root/Invoke-LserpSelectOnlyProfilePreflight.ps1" "$stage/Deployment/"
cp "$deployment_root/New-WorkflowUatAuthorization.ps1" "$stage/Deployment/"
cp "$deployment_root/New-WorkflowWriteUatCampaign.ps1" "$stage/Deployment/"
cp "$deployment_root/Test-WorkflowWriteUatCampaign.ps1" "$stage/Deployment/"
cp "$deployment_root/workflow-write-uat-case-catalog.v1.json" "$stage/Deployment/"
cp "$deployment_root/New-CustomerAcceptanceBundle.ps1" "$stage/Deployment/"
cp "$deployment_root/Build-LegacyErpAcceptance.ps1" "$stage/Deployment/"
cp "$deployment_root/Sign-LserpAgentPetHost.ps1" "$stage/Deployment/"
cp "$deployment_root/CommercialPackage/Test-DeploymentContracts.ps1" "$stage/Deployment/"
cp "$deployment_root/business-adapters.example.json" "$stage/Deployment/"
cp "$deployment_root/command-rollout.example.json" "$stage/Deployment/"
cp "$deployment_root/SqlServer/"*.sql "$stage/Deployment/SqlServer/"
cp "$deployment_root/customer-profiles/"*.json "$stage/Deployment/customer-profiles/"
cp "$deployment_root/customer-profiles/"*.sql "$stage/Deployment/customer-profiles/"
cp "$deployment_root/customer-profiles/README.md" "$stage/Deployment/customer-profiles/"
cp "$deployment_root/CommercialPackage/README.md" "$stage/README.md"
cp "$deployment_root/CommercialPackage/Start-LserpAgentPet.ps1" "$stage/Start-LserpAgentPet.ps1"
cp "$deployment_root/CommercialPackage/Verify-LserpCommercialPackage.ps1" "$stage/Verify-LserpCommercialPackage.ps1"
cp "$repo_root/插件库/Contracts/erp-agent-wire-contract-v1.json" "$stage/Contracts/"
# The package version is also the version reported by the bundled bridge CLI.
# Keep customer-facing runbooks and handoff templates bound to that exact
# version; a source checkout may be used to produce more than one package
# version, so these documents must not carry a stale release number.
"$python_bin" - "$stage" "$package_version" <<'PY'
import json
import pathlib
import sys
stage = pathlib.Path(sys.argv[1])
package_version = sys.argv[2]
marker = "@LSERP_PACKAGE_VERSION@"
relative_paths = [
"README.md",
"Deployment/CUSTOMER_ACCEPTANCE.md",
"Deployment/WRITE_ACCEPTANCE.md",
"Deployment/field-readonly-validation.example.json",
]
for relative in relative_paths:
path = stage / relative
if not path.is_file():
raise SystemExit("package version binding source is missing: " + relative)
text = path.read_text(encoding="utf-8")
if marker not in text:
raise SystemExit("package version marker is missing: " + relative)
rendered = text.replace(marker, package_version)
if marker in rendered:
raise SystemExit("package version marker was not fully rendered: " + relative)
path.write_text(rendered, encoding="utf-8")
template = json.loads(
(stage / "Deployment/field-readonly-validation.example.json")
.read_text(encoding="utf-8")
)
expected_cli_version = template.get("cli", {}).get("version")
expected_cli_path = template.get("cli", {}).get("path")
if expected_cli_version != package_version or package_version not in expected_cli_path:
raise SystemExit("rendered field validation template is not package-version bound")
PY
git_commit="unknown"
worktree_dirty=true
if git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git_commit="$(git -C "$repo_root" rev-parse HEAD)"
if [[ -z "$(git -C "$repo_root" status --porcelain)" ]]; then
worktree_dirty=false
fi
fi
export LSERP_STAGE="$stage"
export LSERP_LOGS="$logs"
export LSERP_PACKAGE_VERSION_VALUE="$package_version"
export LSERP_GIT_COMMIT_VALUE="$git_commit"
export LSERP_WORKTREE_DIRTY_VALUE="$worktree_dirty"
export LSERP_HOST_AUTHENTICODE_SIGNED_VALUE="$host_authenticode_signed"
export LSERP_HOST_CERT_THUMBPRINT_VALUE="$host_certificate_thumbprint"
export LSERP_ASTRBOT_CONTRACT_EVIDENCE="$astrbot_contract_evidence"
"$python_bin" - <<'PY'
import hashlib
import json
import os
import pathlib
import re
import sys
from datetime import datetime, timezone
stage = pathlib.Path(os.environ["LSERP_STAGE"])
logs = pathlib.Path(os.environ["LSERP_LOGS"])
def parse_pair(name: str) -> tuple[int, int]:
text = (logs / name).read_text(encoding="utf-8", errors="replace")
matches = re.findall(r"passed=(\d+) failed=(\d+)", text)
if not matches:
raise SystemExit(f"missing test summary in {name}")
return tuple(map(int, matches[-1]))
kernel = parse_pair("kernel.log")
host = parse_pair("host.log")
deployment = parse_pair("deployment.log")
sql_contract = parse_pair("sql-contract.log")
node_text = (logs / "node.log").read_text(encoding="utf-8", errors="replace")
python_text = (logs / "python.log").read_text(encoding="utf-8", errors="replace")
sql_contract_text = (logs / "sql-contract.log").read_text(
encoding="utf-8", errors="replace"
)
node_match = re.findall(
r"(?m)^\s*(?:#|)\s+tests\s+(\d+)\s*$",
node_text,
)
python_match = re.findall(r"Ran (\d+) tests", python_text)
if not node_match or not python_match:
raise SystemExit("missing Node or Python test summary")
python_skipped_match = re.findall(r"skipped=(\d+)", python_text)
python_skipped = int(python_skipped_match[-1]) if python_skipped_match else 0
if python_skipped != 0:
raise SystemExit("commercial AstrBot verification contains skipped tests")
astrbot_contract = json.loads(
pathlib.Path(os.environ["LSERP_ASTRBOT_CONTRACT_EVIDENCE"]).read_text(
encoding="utf-8"
)
)
expected_astrbot_contract_keys = {
"schemaVersion",
"passed",
"repository",
"sourceTag",
"sourceCommit",
"runtimeVersion",
"versionSpecifier",
"pluginVersion",
"licenseExpression",
"licenseSha256",
"eulaSha256",
"criticalSourceFilesVerified",
"registeredTools",
}
if (
not isinstance(astrbot_contract, dict)
or set(astrbot_contract) != expected_astrbot_contract_keys
or astrbot_contract["schemaVersion"] != "1.1"
or astrbot_contract["passed"] is not True
or astrbot_contract["repository"]
!= "https://github.com/AstrBotDevs/AstrBot.git"
or astrbot_contract["sourceTag"] != "v4.27.2"
or astrbot_contract["sourceCommit"]
!= "ad4fbfa90ca0c4ac2b30b3250e34dbf8fe7babbf"
or astrbot_contract["runtimeVersion"] != "4.27.2"
or astrbot_contract["versionSpecifier"] != "==4.27.2"
or astrbot_contract["pluginVersion"] != "0.4.0"
or astrbot_contract["licenseExpression"] != "AGPL-3.0-or-later"
or astrbot_contract["licenseSha256"]
!= "ccf7d08f932af3e813848881731113afbb7c80d0fd6d958e8d319002bf344d02"
or astrbot_contract["eulaSha256"]
!= "c332de7781e87c67d6d3beda463fa04705075a6bae9e52a252f7c639f6defd80"
or astrbot_contract["criticalSourceFilesVerified"] != 20
or astrbot_contract["registeredTools"]
!= ["erp_get_context", "erp_get_capabilities", "erp_plan_command"]
):
raise SystemExit("actual AstrBot runtime contract evidence is invalid")
guga_audit_path = stage / "Deployment" / "guga-upstream-audit.v1.json"
guga_audit_bytes = guga_audit_path.read_bytes()
guga_audit = json.loads(guga_audit_bytes.decode("utf-8"))
asset = guga_audit.get("asset") if isinstance(guga_audit, dict) else None
installer = guga_audit.get("installer") if isinstance(guga_audit, dict) else None
service_source = (
guga_audit.get("serviceSource") if isinstance(guga_audit, dict) else None
)
commercial_decision = (
guga_audit.get("commercialDecision")
if isinstance(guga_audit, dict)
else None
)
if (
not isinstance(guga_audit, dict)
or set(guga_audit) != {
"schemaVersion",
"auditedAtUtc",
"asset",
"installer",
"serviceSource",
"commercialDecision",
}
or guga_audit["schemaVersion"] != "1.0"
or guga_audit["auditedAtUtc"] != "2026-08-13T19:31:24+00:00"
or not isinstance(asset, dict)
or set(asset)
!= {
"id",
"displayName",
"ownerHandle",
"ownerName",
"uploadedAtUtc",
"shareUrl",
"shareDataUrl",
"downloadUrl",
"packageSha256",
"packageSizeBytes",
"manifestSha256",
"spriteSha256",
"spriteSizeBytes",
"atlasSize",
"licenseMetadataPresent",
"licenseFilePresent",
}
or asset["id"] != "guga"
or asset["ownerHandle"] != "circus"
or asset["shareUrl"] != "https://codex-pets.net/share/guga"
or asset["packageSha256"]
!= "3ebd971ba59a0c988a6be0924669b4c5db9234bcc5d17d506e34eba332e6021f"
or asset["packageSizeBytes"] != 1946012
or asset["manifestSha256"]
!= "f9f715811c26ca610764a7698e28f2e182882f097f4c60a3f00a79dd7530bd20"
or asset["spriteSha256"]
!= "1b61ea2af98717b9ebe55beb4c6b820b89e9c42d4fdfeca21cf63ed3ad4e38da"
or asset["spriteSizeBytes"] != 1945586
or asset["atlasSize"] != "1536x1872"
or asset["licenseMetadataPresent"] is not False
or asset["licenseFilePresent"] is not False
or not isinstance(installer, dict)
or set(installer)
!= {
"packageName",
"version",
"registryUrl",
"tarballUrl",
"tarballSha1",
"tarballSha256",
"npmIntegrity",
"declaredLicense",
"repositoryDeclared",
"defaultApiBase",
"installRoot",
"writtenFiles",
"assetDigestVerification",
"assetSignatureVerification",
"assetLicenseVerification",
}
or installer["packageName"] != "codex-pets"
or installer["version"] != "0.3.0"
or installer["tarballSha1"] != "82e41349ae63eb9e63099f2e06a56468182e2c90"
or installer["tarballSha256"]
!= "9ec8bf1ea09e6d8fdc17b33a594a178a9b20bd3dc6decbb22973758394c9c1c7"
or installer["npmIntegrity"]
!= "sha512-b7PjV0phEK7jn0rnyXzh3LMIsAdqSUf75mCdSxZEyIuFScCxwOeUZoDxnWj97rfg4ihk6XLaKvg6fgWD+CWcAQ=="
or installer["declaredLicense"] != "MIT"
or installer["repositoryDeclared"] is not False
or installer["installRoot"] != "$CODEX_HOME/pets/{pet-id}"
or installer["writtenFiles"] != ["pet.json", "spritesheet.webp"]
or installer["assetDigestVerification"] is not False
or installer["assetSignatureVerification"] is not False
or installer["assetLicenseVerification"] is not False
or not isinstance(service_source, dict)
or set(service_source)
!= {
"repository",
"commit",
"softwareLicense",
"licenseSha256",
"termsSourceSha256",
"termsEffectiveDate",
"uploadTermsScope",
}
or service_source["repository"]
!= "https://github.com/portons/codex-pet-share"
or service_source["commit"]
!= "22725091da2787e8e525c9289cb7826a34be4950"
or service_source["softwareLicense"] != "MIT"
or service_source["licenseSha256"]
!= "13e779572adacb503b7e7a0c676571fcd86114a73f6aa000412c24a9a06a97d3"
or service_source["termsSourceSha256"]
!= "70ad12414864566b8cd469a7d2ca39fe60050686cecacc126ff1aca587f790bb"
or service_source["uploadTermsScope"] != "public-sharing-through-service"
or not isinstance(commercial_decision, dict)
or set(commercial_decision)
!= {"status", "code", "reason", "requiredEvidence"}
or commercial_decision["status"] != "external-license-required"
or commercial_decision["code"] != "guga_commercial_license_missing"
or commercial_decision["requiredEvidence"]
!= [
"rights-holder-identity",
"commercial-product-use",
"customer-deployment-and-copying",
"product-display",
"territory-and-term",
"asset-sha256-binding",
"authorized-legal-review",
]
):
raise SystemExit("guga upstream supply-chain audit is invalid")
guga_audit_sha256 = hashlib.sha256(guga_audit_bytes).hexdigest()
banned_guga_hashes = {
asset["packageSha256"],
asset["manifestSha256"],
asset["spriteSha256"],
}
for path in stage.rglob("*"):
if not path.is_file():
continue
lower_name = path.name.lower()
if lower_name == "spritesheet.webp" or lower_name.endswith(".codex-pet.zip"):
raise SystemExit(
f"external guga asset must not be bundled: {path.relative_to(stage)}"
)
if path.stat().st_size <= 20 * 1024 * 1024:
digest = hashlib.sha256(path.read_bytes()).hexdigest()
if digest in banned_guga_hashes:
raise SystemExit(
f"external guga asset bytes must not be bundled: {path.relative_to(stage)}"
)
minimum_pass_counts = {
"commandKernel": 284,
"desktopHost": 50,
"petWebUi": 24,
"astrbotPlugin": 96,
"deploymentContracts": 76,
"sqlServerCompatibility100": 16,
}
actual_pass_counts = {
"commandKernel": kernel[0],
"desktopHost": host[0],
"petWebUi": int(node_match[-1]),
"astrbotPlugin": int(python_match[-1]),
"deploymentContracts": deployment[0],
"sqlServerCompatibility100": sql_contract[0],
}
if kernel[1] != 0 or host[1] != 0 or deployment[1] != 0 or sql_contract[1] != 0:
raise SystemExit("commercial verification contains failed tests")
if (
"parserPackage=Microsoft.SqlServer.TransactSql.ScriptDom "
"parserVersion=180.59.2 dialect=TSql100"
) not in sql_contract_text:
raise SystemExit("commercial SQL Server compatibility parser metadata is invalid")
for suite, minimum in minimum_pass_counts.items():
if actual_pass_counts[suite] < minimum:
raise SystemExit(
f"commercial verification test baseline regressed: {suite}"
)
report = {
"schemaVersion": "1.1",
"packageVersion": os.environ["LSERP_PACKAGE_VERSION_VALUE"],
"generatedAtUtc": datetime.now(timezone.utc).isoformat(),
"sourceCommit": os.environ["LSERP_GIT_COMMIT_VALUE"],
"sourceWorktreeDirty": os.environ["LSERP_WORKTREE_DIRTY_VALUE"] == "true",
"deliveryTopology": {
"desktopBundleContainsLserpCli": False,
"desktopBundleContainsBridgeCli": True,
"bridgeCliPath": "Host/lserp-agent-cli.exe",
"bridgeCliDatabaseDirectAccess": False,
"bridgeCliPublishMode": "win_x64_single_file_self_contained",
"desktopBundleContainsLegacyErp": False,
"legacyArtifactMode": "separate_signed_windows_build",
"legacyBuildTool": "Deployment/Build-LegacyErpAcceptance.ps1",
"runtimeRequiresLegacyArtifact": True,
},
"automatedVerification": {
"commandKernel": {"passed": actual_pass_counts["commandKernel"], "failed": kernel[1]},
"desktopHost": {"passed": actual_pass_counts["desktopHost"], "failed": host[1]},
"petWebUi": {"passed": actual_pass_counts["petWebUi"], "failed": 0},
"astrbotPlugin": {
"passed": actual_pass_counts["astrbotPlugin"],
"failed": 0,
"skipped": python_skipped,
},
"astrbotRuntimeContract": astrbot_contract,
"miniMaxVision": {
"mode": "direct_https_vlm",
"allowedRegions": ["global", "cn"],
"bundledCli": False,
},
"gugaSupplyChainAudit": {
"passed": True,
"auditFile": "Deployment/guga-upstream-audit.v1.json",
"auditSha256": guga_audit_sha256,
"assetId": "guga",
"installerPackage": "codex-pets",
"installerVersion": "0.3.0",
"installerTarballSha256": installer["tarballSha256"],
"observedSpriteSha256": asset["spriteSha256"],
"upstreamCommercialLicensePresent": False,
"assetBundled": False,
},
"deploymentContracts": {
"passed": actual_pass_counts["deploymentContracts"],
"failed": deployment[1],
},
"sqlServerCompatibility100": {
"passed": actual_pass_counts["sqlServerCompatibility100"],
"failed": sql_contract[1],
"parserPackage": "Microsoft.SqlServer.TransactSql.ScriptDom",
"parserVersion": "180.59.2",
"dialect": "TSql100",
},
"legacyNet40ApiCompile": True,
"winX64SelfContainedPublish": True,
"bridgeCliWinX64SelfContainedPublish": True,
"hostAuthenticode": {
"signed": os.environ["LSERP_HOST_AUTHENTICODE_SIGNED_VALUE"] == "true",
"certificateThumbprint": (
os.environ["LSERP_HOST_CERT_THUMBPRINT_VALUE"].upper() or None
),
},
},
"releaseReadiness": False,
"remainingHardGates": [
"clean reviewed source commit",
"valid Windows Authenticode signature and signed installer",
"written commercial license for the external guga artwork",
"approved AstrBot AGPL-3.0-or-later and EULA compliance plan",
"approved MiniMax API service terms, data processing, deployment region and billing plan",
"customer Windows/.NET Framework 4/DevExpress 15.2 integration",
"customer SQL Server transactional write, rollback, idempotency and audit evidence",
"signed workflow acceptance manifests bound to final business-adapters.json",
],
}
(stage / "BUILD-VERIFICATION.json").write_text(
json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
secret_patterns = [
re.compile("sk" + r"-cp-[A-Za-z0-9_-]{20,}"),
re.compile(r"(?i)(?:api[_-]?key|password)\s*[=:]\s*['\"][A-Za-z0-9_-]{16,}['\"]"),
]
for path in stage.rglob("*"):
if not path.is_file() or path.stat().st_size > 2 * 1024 * 1024:
continue
if path.suffix.lower() not in {".json", ".yaml", ".yml", ".md", ".ps1", ".py", ".js", ".sql", ".txt"}:
continue
text = path.read_text(encoding="utf-8", errors="ignore")
if any(pattern.search(text) for pattern in secret_patterns):
raise SystemExit(f"possible secret in staged text file: {path.relative_to(stage)}")
files = []
for path in sorted(stage.rglob("*"), key=lambda item: item.as_posix()):
if not path.is_file() or path.name == "SHA256SUMS.json":
continue
digest = hashlib.sha256(path.read_bytes()).hexdigest()
files.append({
"path": path.relative_to(stage).as_posix(),
"sizeBytes": path.stat().st_size,
"sha256": digest,
})
manifest = {
"schemaVersion": "1.0",
"packageVersion": os.environ["LSERP_PACKAGE_VERSION_VALUE"],
"generatedAtUtc": datetime.now(timezone.utc).isoformat(),
"files": files,
}
(stage / "SHA256SUMS.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
PY
archive_base="$temporary_root/$package_name"
"$python_bin" - "$stage" "$archive_base" <<'PY'
import pathlib
import shutil
import sys
stage = pathlib.Path(sys.argv[1])
archive_base = pathlib.Path(sys.argv[2])
shutil.make_archive(str(archive_base), "zip", root_dir=stage.parent, base_dir=stage.name)
PY
mv "$stage" "$final_directory"
mv "$archive_base.zip" "$final_archive"
"$python_bin" - "$final_archive" <<'PY'
import hashlib
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
print(f"package={path}")
print(f"sizeBytes={path.stat().st_size}")
print(f"sha256={hashlib.sha256(path.read_bytes()).hexdigest()}")
PY