feat: add ERP agent pet bridge and startup guide

This commit is contained in:
郎速科技
2026-08-14 14:28:28 +08:00
parent a803070819
commit 4c08f4c948
252 changed files with 134752 additions and 72 deletions
@@ -0,0 +1,719 @@
#!/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