fix(backup): align Host110 runtime manifest staging
This commit is contained in:
@@ -384,7 +384,7 @@ if (Test-Path -LiteralPath $evidencePath -PathType Leaf) {
|
||||
}
|
||||
|
||||
$sourceRoot = Join-Path $SourceRootBase "host110-backup-source-$RunId"
|
||||
$manifestPath = Join-Path $sourceRoot "agent99-host110-backup-runtime-manifest.json"
|
||||
$manifestPath = Join-Path $sourceRoot "manifest.json"
|
||||
$remoteStage = "/tmp/agent99-host110-backup-runtime-$RunId"
|
||||
$startedAt = [DateTime]::UtcNow
|
||||
$mutex = [Threading.Mutex]::new($false, $TransportMutexName)
|
||||
|
||||
78
docs/proposals/AWOOOI-AG99-H110-BACKUP-RUNTIME-V8.md
Normal file
78
docs/proposals/AWOOOI-AG99-H110-BACKUP-RUNTIME-V8.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# AWOOOI Agent99 Host110 Backup Runtime V8
|
||||
|
||||
Status: proposed; owner approval required before any production apply.
|
||||
|
||||
This artifact supersedes V2 through V7. The V7 candidate, revision, artifact,
|
||||
and diff hashes are void for approval. All V5 hashes remain void. Prior refs
|
||||
remain immutable audit evidence only; none is production execution authority.
|
||||
|
||||
## Scope
|
||||
|
||||
- Agent99 runtime bundle: 19 files, guarded by one producer/consumer parity
|
||||
test.
|
||||
- Host110 backup payload: 18 files, comprising 17 backup scripts and
|
||||
`backup-health-textfile-exporter.py`.
|
||||
- Host110 remote stage: 21 unique basenames, comprising the 18 payload files,
|
||||
the fixed executor, the independent verifier, and `manifest.json`.
|
||||
- Fixed target: `wooo@192.168.0.110`, dispatched by Windows99 Agent99 from an
|
||||
exact Gitea main revision and digest manifest.
|
||||
- Gitea offline backup and container lifecycle changes remain excluded.
|
||||
`backup-gitea.sh` has no candidate diff.
|
||||
|
||||
## V7 Defect Closed
|
||||
|
||||
V7 created and uploaded
|
||||
`agent99-host110-backup-runtime-manifest.json`, while the fixed Host110
|
||||
executor consumed `$SOURCE_STAGE/manifest.json`. V8 uses `manifest.json` at
|
||||
the broker source stage, so SCP preserves the exact basename consumed by the
|
||||
executor.
|
||||
|
||||
The integration test derives all broker SCP basenames from the actual
|
||||
PowerShell file list and manifest path, requires 21 unique stage files, and
|
||||
matches the manifest and verifier basenames to the executor's exact
|
||||
`$SOURCE_STAGE` paths. A hand-built temporary manifest cannot satisfy this
|
||||
test by itself.
|
||||
|
||||
## Exact Diff Serialization
|
||||
|
||||
Substitute the V8 Gitea live-ref SHA for `<candidate_git_sha>` and hash the
|
||||
exact stdout bytes from this command:
|
||||
|
||||
```sh
|
||||
git diff --binary --full-index \
|
||||
e7e3bcf8a5a8a19c1253d73f51d9dca248af0aa3..<candidate_git_sha> |
|
||||
shasum -a 256
|
||||
```
|
||||
|
||||
The review message independently binds the Gitea live ref and Git SHA, this
|
||||
tracked artifact path and bytes hash, and the serialized diff hash.
|
||||
|
||||
## Apply Effects
|
||||
|
||||
- Windows99 Agent99 runtime promotion writes the 19 runtime files plus bounded
|
||||
manifest and receipt state.
|
||||
- Host110 Apply writes/replaces the 18 payload paths through one exclusive,
|
||||
digest-bound filesystem transaction.
|
||||
- Apply does not run a backup, invoke a Gitea container lifecycle, sync Google
|
||||
Drive, prune or delete snapshots, restart a VM or service, or change a DB,
|
||||
network, or firewall.
|
||||
|
||||
## Validation
|
||||
|
||||
- The exact broker/SCP basename to executor path integration test must pass.
|
||||
- Focused runtime, broker, backup, parity, and Ansible suites must pass.
|
||||
- Ansible and shell/Python static validation must pass.
|
||||
- Changed PowerShell must parse on Windows99 with zero errors.
|
||||
- Central review must independently reproduce the live ref, artifact hash,
|
||||
diff hash, 19/18/21 counts, and zero `backup-gitea.sh` diff.
|
||||
|
||||
## Rollback
|
||||
|
||||
- Source rollback reverts the exact V8 diff to
|
||||
`e7e3bcf8a5a8a19c1253d73f51d9dca248af0aa3` without force-pushing.
|
||||
- Agent99 rollback restores the prior runtime files and manifest and verifies
|
||||
the prior hashes independently.
|
||||
- Host110 rollback restores every prior payload's content, SHA-256, uid, gid,
|
||||
and mode under the exclusive lock.
|
||||
- Any mismatch terminates as `rollback_unverified` and cannot be reported as a
|
||||
successful rollback.
|
||||
@@ -50,6 +50,22 @@ def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _powershell_string(source: str, variable: str) -> str:
|
||||
match = re.search(rf'\${re.escape(variable)}\s*=\s*"([^"]+)"', source)
|
||||
assert match is not None, variable
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def _powershell_array(source: str, variable: str) -> tuple[str, ...]:
|
||||
match = re.search(
|
||||
rf'\${re.escape(variable)}\s*=\s*@\((.*?)\n\)',
|
||||
source,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match is not None, variable
|
||||
return tuple(re.findall(r'"([^"]+)"', match.group(1)))
|
||||
|
||||
|
||||
def test_windows99_broker_fetches_exact_gitea_revision_and_is_bounded() -> None:
|
||||
source = BROKER.read_text(encoding="utf-8")
|
||||
|
||||
@@ -75,6 +91,34 @@ def test_windows99_broker_fetches_exact_gitea_revision_and_is_bounded() -> None:
|
||||
assert 'Invoke-Agent99BoundedScp $sourcePackage.localPaths' in source
|
||||
|
||||
|
||||
def test_broker_scp_basenames_match_executor_source_stage_contract() -> None:
|
||||
broker = BROKER.read_text(encoding="utf-8")
|
||||
executor = EXECUTOR.read_text(encoding="utf-8")
|
||||
|
||||
runtime_paths = _powershell_array(broker, "RuntimeRelativePaths")
|
||||
executor_path = _powershell_string(broker, "ExecutorRelativePath")
|
||||
verifier_path = _powershell_string(broker, "VerifierRelativePath")
|
||||
manifest_match = re.search(
|
||||
r'\$manifestPath\s*=\s*Join-Path\s+\$sourceRoot\s+"([^"]+)"',
|
||||
broker,
|
||||
)
|
||||
assert manifest_match is not None
|
||||
manifest_name = manifest_match.group(1)
|
||||
|
||||
staged_basenames = tuple(
|
||||
Path(path).name
|
||||
for path in (*runtime_paths, executor_path, verifier_path, manifest_name)
|
||||
)
|
||||
assert len(staged_basenames) == 21
|
||||
assert len(set(staged_basenames)) == 21
|
||||
assert manifest_name == "manifest.json"
|
||||
assert '$localPaths += $executorPath, $verifierPath, $ManifestPath' in broker
|
||||
assert 'Invoke-Agent99BoundedScp $sourcePackage.localPaths $remoteStage' in broker
|
||||
assert f'$SOURCE_STAGE/{manifest_name}' in executor
|
||||
assert f'$SOURCE_STAGE/{Path(verifier_path).name}' in executor
|
||||
assert Path(executor_path).name == "host110-backup-runtime-executor.sh"
|
||||
|
||||
|
||||
def test_windows99_broker_verify_is_independent_no_write_and_evidence_is_immutable() -> None:
|
||||
source = BROKER.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user