Status: exact-version, fail-closed contract. Retrieved official sources: 2026-07-25.
This document is the operator-facing contract for the one live Baota mutation reserved by SiteOps. It stages a validated ZIP archive in a new, release-owned directory for a non-production static site. It does not change the site's traffic, document root, runtime, database, or promotion pointer.
The only evidenced live version is exactly 11.7.0. This contract does not
claim support for other 11.7.x versions. The site.static.stage capability
stays unavailable until the exact endpoint, action, and response-schema
preflight described here succeeds against the selected target.
The following facts come from the linked Baota API documentation. The source documents define the HTTP method, action names, and fields. SiteOps adds the validation, containment, capability, and evidence rules in the next section.
| Source | Retrieved | Use in this contract |
|---|---|---|
| Baota API | 2026-07-25 | API documentation index |
| getData | 2026-07-25 | Read the site inventory |
| CreateFile | 2026-07-25 | Create the empty staging child |
| UploadFile | 2026-07-25 | Upload the validated ZIP |
| mutil_unzip | 2026-07-25 | Extract the uploaded ZIP |
| GetDir | 2026-07-25 | Read the staged file tree |
| test_path | 2026-07-25 | Check a staged path |
Only the six calls in this table are allowed. A client must not expose a
generic action, endpoint, file-manager, or command request. The fieldNames
column lists the action-specific fields for that call. Every authenticated
request also carries the global transport auth fields request_time and
request_token declared in authFieldNames below.
| Exact call | Encoding | Required action-specific fields |
|---|---|---|
POST /data action=getData table=sites type=-1 |
form | action=getData, table=sites, type=-1 |
POST /files action=CreateDir path |
form | action=CreateDir, path |
POST /files action=UploadFile path zunfile request_time request_token |
multipart | action=UploadFile, path, zunfile |
POST /files action=mutil_unzip sfile_list dfile coding=utf-8 type1=zip |
form | action=mutil_unzip, sfile_list, dfile, coding=utf-8, type1=zip |
POST /files action=GetDir path |
form | action=GetDir, path |
POST /files action=test_path path |
form | action=test_path, path |
The documented mutil_unzip response shape is an object containing
status, msg, success, and fail. A target's observed response schema is
recorded during preflight. SiteOps does not substitute a different schema or
infer completion from one boolean field.
These rules are SiteOps policy, not claims about Baota's sandbox or guarantees.
- The client sends
POST /data action=getData table=sites type=-1and matches the requested site by exactdata[].nameequality. Zero matches fail. More than one match fails. Partial, case-insensitive, or substring matches are not accepted. - For every authenticated request,
request_timeis the current Unix time in seconds.request_tokenis exactlyMD5(request_time + MD5(api_sk)). Both values are fresh per request. These transport auth fields are recorded as the ordered global listauthFieldNamesand are added to every authenticated request, including the multipartUploadFilecall. ThefieldNamesof each allowlisted call list only the action-specific fields;request_timeandrequest_tokenare never duplicated in per-callfieldNames. - This contract makes no claim of TTL or replay protection. HTTPS with normal certificate validation and IP allowlisting are mandatory SiteOps policy.
- The credential is referenced by a local credential reference. The API key and derived token are never written to a receipt or evidence record.
The input is a ZIP archive only. Validation happens before any remote mutation.
| Limit or rule | SiteOps value |
|---|---|
| Archive type | ZIP only |
| Compressed size | <= 64 MiB |
| Uncompressed size | <= 256 MiB |
| Regular file count | <= 5,000 |
| Individual regular file size | <= 32 MiB |
| Normalized path encoding | UTF-8 POSIX relative path |
| Normalized path length | <= 240 bytes |
Reject absolute paths, .. path components, backslashes, NUL bytes, duplicate
normalized names, duplicate case-folded normalized names, symlinks, hardlinks,
devices, FIFOs, and sockets. Directories are allowed only as archive
structure, not as additional file types. The archive is never treated as a
source of remote paths.
The operator configures one absolute, dedicated stagingRoot. SiteOps derives
one child directory from the release UUID and artifact digest. The caller does
not supply a remote path. Every normalized request path must be strictly
beneath stagingRoot. The root and derived child must not be the production
document root, and the child must be absent before CreateDir is sent.
This is client-side containment, not a Baota sandbox. It cannot defend a compromised panel or root account, and it cannot defend concurrent out-of-band mutation of the configured root or child.
The official Baota 11.7.0 documentation does not document an asynchronous
extraction threshold. Current 11.8 source code observed outside the 11.7.0
documentation set queues extraction at >= 500 MiB. That 11.8 observation is
corroborative only; this contract does not claim the 500 MiB threshold is an
official 11.7.0 behavior. Corroborative only.
SiteOps's compressed limit is <= 64 MiB, which is below the observed 11.8
queueing threshold. The SiteOps slice therefore does not depend on
asynchronous queueing, and SiteOps does not label below-threshold work as
asynchronous. status:true from mutil_unzip does not prove that extraction
has completed and does not provide atomic, durable, retryable, or idempotent
completion semantics.
After each mutation, the client performs bounded read-after-write checks with
the allowlisted GetDir and test_path calls. The receipt records the
observed mutil_unzip response schema baseline and the postcondition
observations. A timeout, connection reset, or malformed mutation response is
unknown_outcome; there is no automatic retry.
site.static.stage is granted only when all of these facts are true:
| Preflight fact | Required value |
|---|---|
| Panel version | exact 11.7.0 |
| Endpoint, action, and response schema | exact observed preflight for all six allowlisted calls |
| Staging root | configured absolute dedicated stagingRoot |
| Transport | HTTPS with normal certificate validation |
| Credential | valid local credential reference |
| Site | exact non-production static site |
| Runtime | static, not Node, PHP, Docker, or another runtime |
Any other version, partial endpoint set, missing or malformed response,
missing root, missing credential reference, failed HTTPS policy, production
site, or unsupported runtime is unavailable. The stable reasons used by the
verifier and by capability evidence are version_missing,
unsupported_version, staging_root_missing, and runtime_unsupported for
the built-in negative fixtures. Other failed checks use the corresponding
stable reason named by the failed preflight field.
The one live mutation is writing the validated ZIP into the newly created
staging child and extracting it there. No operation in this contract provides
idempotency, remote checksum verification, atomicity, overwrite, delete,
promotion, rollback, runtime deployment, database migration, a generic
file-manager, shell execution, or automatic AI execution. The ordered list
excludedOperations in the machine contract enumerates these forbidden
operations in their canonical order, and the verifier enforces that exact
order.
The receipt outcome is exactly staged only; traffic unchanged. It is emitted
only after the bounded postconditions are recorded. A blocked preflight or an
unknown_outcome never receives this success wording.
Every receipt and evidence record includes these fields:
| Field | Required value |
|---|---|
targetDigest |
digest of the selected target identity |
siteDigest |
digest of the exact matched site record |
releaseDigest |
digest of the release identity |
artifactDigest |
digest of the validated archive/artifact |
capabilityGeneration |
generation used by the authorization decision |
capabilityDigest |
digest of the exact capability snapshot |
normalizedStagingChild |
derived child, after local normalization |
endpointSequence |
ordered six-call sequence actually attempted |
responseSchemaDigests |
digest for each observed response schema |
postconditionObservations |
bounded GetDir and test_path observations |
timestamps |
preflight, request, mutation, and observation times |
outcome |
staged, blocked, or unknown_outcome |
redactedErrorCode |
stable error code without credentials or tokens |
The verifier parses the JSON block below. It checks identifiers, tables, limits, source links, and policy values. It does not snapshot or compare this document's prose.
{
"contract": "baota-static-stage",
"contractVersion": "1",
"supportedVersion": "11.7.0",
"versionPolicy": "exact",
"capability": "site.static.stage",
"officialSources": [
"https://docs.bt.cn/api/",
"https://docs.bt.cn/api/data/getData",
"https://docs.bt.cn/api/files/CreateFile",
"https://docs.bt.cn/api/files/UploadFile",
"https://docs.bt.cn/api/files/mutil_unzip",
"https://docs.bt.cn/api/files/GetDir",
"https://docs.bt.cn/api/files/test_path"
],
"retrieved": "2026-07-25",
"allowlistedCalls": [
{
"method": "POST",
"path": "/data",
"action": "getData",
"encoding": "form",
"fieldNames": ["action", "table", "type"],
"fixedFields": { "table": "sites", "type": -1 }
},
{
"method": "POST",
"path": "/files",
"action": "CreateDir",
"encoding": "form",
"fieldNames": ["action", "path"]
},
{
"method": "POST",
"path": "/files",
"action": "UploadFile",
"encoding": "multipart",
"fieldNames": ["action", "path", "zunfile"]
},
{
"method": "POST",
"path": "/files",
"action": "mutil_unzip",
"encoding": "form",
"fieldNames": ["action", "sfile_list", "dfile", "coding", "type1"],
"fixedFields": { "coding": "utf-8", "type1": "zip" }
},
{
"method": "POST",
"path": "/files",
"action": "GetDir",
"encoding": "form",
"fieldNames": ["action", "path"]
},
{
"method": "POST",
"path": "/files",
"action": "test_path",
"encoding": "form",
"fieldNames": ["action", "path"]
}
],
"siteMatch": {
"field": "data[].name",
"comparison": "exact",
"zeroMatches": "fail",
"multipleMatches": "fail"
},
"auth": {
"requestTime": "Unix seconds",
"requestToken": "MD5(request_time + MD5(api_sk))",
"freshPerRequest": true,
"ttlOrReplayProtectionClaim": false,
"https": "normal certificate validation",
"ipAllowlisting": true
},
"authFieldNames": ["request_time", "request_token"],
"archive": {
"type": "ZIP only",
"compressedMiBMax": 64,
"uncompressedMiBMax": 256,
"regularFileCountMax": 5000,
"regularFileMiBMax": 32,
"pathEncoding": "UTF-8",
"pathStyle": "POSIX relative",
"pathBytesMax": 240,
"forbidden": [
"absolute paths",
"..",
"backslashes",
"NUL",
"duplicate normalized names",
"duplicate normalized case-folded names",
"symlinks",
"hardlinks",
"devices",
"FIFOs",
"sockets"
]
},
"remotePaths": {
"rootName": "stagingRoot",
"root": "absolute dedicated operator configuration",
"childDerivedFrom": ["release UUID", "artifact digest"],
"callerSuppliedRemotePath": false,
"normalizedRequestPath": "strictly beneath stagingRoot",
"productionDocumentRoot": false,
"childAbsentBeforeCreation": true,
"containment": "client-side containment, not a Baota sandbox",
"outOfBandMutationDefense": false
},
"completion": {
"corroborativeAsynchronousThresholdMiB": 500,
"corroborativeAsynchronousThresholdSource": "11.8 source",
"statusTrueMeansComplete": false,
"postconditions": ["GetDir", "test_path"],
"observedMutilUnzipSchemaRequired": true,
"officialMutilUnzipSchema": ["status", "msg", "success", "fail"]
},
"capabilityPreflight": {
"requiredVersion": "11.7.0",
"requiredEndpointActionSchema": "exact observed preflight",
"requiredStagingRoot": true,
"requiredHttps": true,
"requiredCredentialReference": true,
"requiredNonProductionSite": true,
"requiredRuntime": "static",
"grant": "site.static.stage",
"partialOrMalformedUnavailable": true
},
"negativeFixtures": [
{
"name": "missing_version",
"reason": "version_missing",
"capability": null
},
{
"name": "version_99_0",
"reason": "unsupported_version",
"capability": null
},
{
"name": "missing_staging_root",
"reason": "staging_root_missing",
"capability": null
},
{
"name": "node_runtime",
"reason": "runtime_unsupported",
"capability": null
}
],
"unknownOutcome": {
"inputs": ["timeout", "connection reset", "malformed mutation response"],
"result": "unknown_outcome",
"automaticRetry": false
},
"excludedOperations": [
"idempotency",
"checksum",
"atomicity",
"overwrite",
"delete",
"promotion",
"rollback",
"runtime deploy",
"DB migration",
"generic file-manager",
"shell",
"automatic AI execution"
],
"receipt": {
"wording": "staged only; traffic unchanged",
"requiredFields": [
"targetDigest",
"siteDigest",
"releaseDigest",
"artifactDigest",
"capabilityGeneration",
"capabilityDigest",
"normalizedStagingChild",
"endpointSequence",
"responseSchemaDigests",
"postconditionObservations",
"timestamps",
"outcome",
"redactedErrorCode"
]
}
}