File size: 7,422 Bytes
da0ce7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# Aureon website release tooling

This directory is the single automated release stack for the static site in
[`website/`](../../website/):

`audit -> commit-bound build -> fresh backup gate -> non-pruning FTPS upload -> exact read-back`

An upload is not publication proof. The release is complete only when the
read-back comparison passes for the exact package.

## 1. Audit

```powershell

python -m scripts.website.audit_site --root website

```

The deterministic audit fails on broken internal links/assets, invalid JSON or
JSON-LD, sitemap/canonical drift, and the static accessibility/SEO contracts it
implements. It is not a substitute for the separate claim, browser, a11y, or
Core Web Vitals gates.

## 2. Build an exact release

The source commit is mandatory. In a Git worktree it must equal checked-out
`HEAD`, and the bounded `website/` tree must be clean. An uncommitted public file
cannot be described as belonging to a commit.

```powershell

$sourceCommit = (git rev-parse HEAD).Trim()

python -m scripts.website.build_package `

  --root website `

  --out artifacts/website-releases `

  --source-commit $sourceCommit

```

The build writes:

- `website_package/` with `index.html` at its root;
- `website_package/HOMEPL_PACKAGE_MANIFEST.txt`, which names the exact source
  commit and package contract;
- `website_package/HOMEPL_FILE_HASHES.json`, a sorted SHA-256 record for every
  other package file, including the summary manifest;
- `aureon-zorza-website.zip`, produced with deterministic ordering and
  timestamps; and
- `aureon-zorza-website.zip.sha256.txt`, the external companion containing the
  ZIP hash and the hash of `HOMEPL_FILE_HASHES.json`.

`HOMEPL_FILE_HASHES.json` intentionally excludes itself to avoid recursive
self-hashing. Its own SHA-256 is in the external companion.

For byte-identical reproduction, also pass the same `--created-at` value. A
standalone test fixture outside Git can build with an explicit valid commit, but
the manifest labels that binding `declared-only-isolated-source`; a real
repository build is labelled `verified-git-head-clean-site`.

## 3. Create and verify a fresh backup

Before any remote write, map the authenticated Home.pl served root and create a
fresh recursive backup with a non-zero manifest. The audited backup tool is
[`website/backup-homepl-ftps.ps1`](../../website/backup-homepl-ftps.ps1). It
emits an `aureon.homepl-backup-transfer.v1` receipt only after a complete,
read-only transfer and manifest verification.

Do not assume that the root is `/` or `/public_html`. The exact current provider
mapping controls. If the existing backup tool's strict root/preflight contract
does not match the current provider mapping, stop and use the Home.pl provider
backup as a named human host gate; do not weaken the mapping check or fabricate
a receipt.

## 4. Plan and deploy without pruning

The Python deployer and the audited PowerShell tools use one environment-name
contract:

| Variable | Required | Meaning |
|---|---:|---|
| `HOMEPL_FTPS_HOST` | yes | FTPS hostname only |
| `HOMEPL_FTPS_USER` | yes | temporary or approved FTPS account |
| `HOMEPL_FTPS_PASSWORD` | yes | process-only secret; never logged |
| `HOMEPL_FTPS_REMOTE_ROOT` | yes | exact authenticated served root |
| `HOMEPL_FTPS_PORT` | yes | exact authenticated port; never inferred from mode |
| `HOMEPL_FTPS_MODE` | yes | exactly `explicit` or `implicit` |
| `HOMEPL_FTPS_CERT_THUMBPRINT` | PowerShell only | optional pinned certificate thumbprint |

Retained Home.pl configuration evidence points to implicit FTPS on port 990.
The Python uploader supports that transport and explicit FTPS as two separate,
fail-closed modes. Both use the operating system trust store with certificate
and hostname verification and require `PROT P`; implicit mode wraps the control
socket before reading the server welcome and never sends `AUTH TLS`, while
explicit mode negotiates TLS after the welcome. Mode and port remain mandatory
and are never inferred from one another. The exact provider hostname, served
root, and approved credentials are still unresolved operator gates, and no live
Home.pl connection or publication is claimed here. Do not silently substitute
explicit port 21.

Dry-run still requires the target binding and process-only credentials because
it prints the exact authenticated-account plan, but it performs no network
operation:

```powershell

python -m scripts.website.ftp_deploy `

  --package artifacts/website-releases/website_package `

  --dry-run

```

A live upload additionally requires the fresh backup transfer receipt:

```powershell

python -m scripts.website.ftp_deploy `

  --package artifacts/website-releases/website_package `

  --backup-receipt C:\absolute\path\to\backup-transfer.json

```

The deployer verifies the local package against its per-file manifest before
opening a connection. It also verifies that the backup receipt was produced by
the checked `website/backup-homepl-ftps.ps1`, that its deterministic CSV
`Path,Bytes,Sha256` manifest matches the exact downloaded directory, and that
its counts, byte totals, root and freshness agree. A hand-authored receipt or a
manifest detached from the backed-up files is refused. The deployer
creates/overwrites package files only. Pruning and remote deletion are disabled;
`--prune` is always refused.

## 5. Compare exact read-back

Capture the served-root files through the authenticated operator or fetch the
public paths into an isolated directory, then compare without credentials or
network access:

```powershell

python -m scripts.website.readback `

  --package artifacts/website-releases/website_package `

  --readback-dir C:\absolute\path\to\captured-served-root

```

The directory helper verifies `HOMEPL_FILE_HASHES.json` itself, every listed file
by size and SHA-256, and the absence of unexpected files in the downloaded
served-root mirror. This exact-mirror rule prevents a mixed release from passing
when non-pruning upload leaves stale public files.

`compare_http_observations()` verifies every expected package path from already
captured status/body objects. It cannot discover an unknown stale URL that was
not requested, so it is expected-path evidence, not proof of an exact served-root
file set. Reports never contain bodies, URLs, headers, cookies, or credentials.

TLS, cache headers, public route rendering, visual regression, accessibility,
claim lint, and Core Web Vitals remain separate mandatory release evidence.

## Manual provider alternatives

Home.pl WebFTP ZIP upload/unzip is a manual host-gate alternative, not a second
release stack. It must use the exact audited ZIP, the same fresh backup gate,
the exact served root, and the same per-file read-back comparison. The legacy
`website/publish-homepl-ftps.ps1` CSV flow and
`website/build-homepl-package.ps1` are not the canonical V45 automated path.
Do not mix their legacy manifests with `HOMEPL_FILE_HASHES.json`.

## Focused verification

```powershell

$env:PYTHONDONTWRITEBYTECODE = '1'

python -m pytest -p no:cacheprovider tests/test_website_tooling.py -q

python -m ruff check scripts/website tests/test_website_tooling.py

python -m mypy --strict scripts/website/build_package.py scripts/website/ftp_deploy.py scripts/website/readback.py

```