Prepare clean 0.2.1 Gate B candidate

This commit is contained in:
mathew 2026-09-30 21:06:15 +01:00
parent 9c29bfcd86
commit 9971a63635
19 changed files with 41 additions and 37 deletions

View File

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>0.2.0</Version>
<Version>0.2.1</Version>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@ -1,6 +1,6 @@
# GuestOps Milestone Report
Version: **0.2.0 release candidate**
Version: **0.2.1 release candidate**
Last updated: **30 September 2026**
This is the working delivery tracker for GuestOps Web. Update a milestone when its state changes and link the pull request, release artifact, test run, or acceptance record that proves the change.
@ -29,7 +29,7 @@ This summary explains what each milestone delivers and where it currently stands
| 16 | Identity, preferences, and privacy | Account/session controls, hotel preferences, privacy inventory, retention decisions, and audit review. | **Implemented; acceptance required.** Legal and operational decisions, identity checks, and independent review remain outstanding. |
| 17 | Inbox usability and desktop parity | Stable pagination, protected unsaved drafts, hotel-timezone display, and desktop workflow parity. | **Implemented; acceptance required.** Automated checks pass; the supervised desktop exercise and independent approval remain outstanding. |
| 18 | Pilot, capacity, and release approval | Capacity proof, incident exercise, five-business-day hotel pilot, findings closure, and Gate B approval. | **In progress.** Validators and targets exist; Gate A/B prerequisites, capacity evidence, incident rehearsal, pilot, and named approvals remain open. |
| 19 | Account security and self-service | TOTP MFA, recovery codes, transactional email, granular roles, preferences, and security notifications. | **Implemented on the development branch; acceptance required.** Keep it separate until `0.2.0` is approved and tagged, then review, merge, and version it as `0.3.0`. |
| 19 | Account security and self-service | TOTP MFA, recovery codes, transactional email, granular roles, preferences, and security notifications. | **Implemented on the development branch; acceptance required.** Keep it separate until `0.2.1` is approved and tagged, then review, merge, and version it as `0.3.0`. |
## Status key
@ -68,7 +68,7 @@ This summary explains what each milestone delivers and where it currently stands
| 15 | Knowledge, AI, and FAQ activation | B | Implemented / acceptance required | Owners can run a bounded no-send batch evaluation, and a release-bound acceptance record enforces positive/negative coverage, zero FAQ errors, separate AI review, staff training, stop-control evidence and named monitoring/rollback owners. Complete the supervised evaluation and retain independent approval. |
| 16 | Identity, preferences, and privacy | B/C | Implemented / acceptance required | Login throttling trusts the client address only after one-hop processing by the configured proxy. A release-bound review now covers owner-controlled preferences, account/session controls, data inventory, retention/deletion/legal-hold ownership, provider decisions, audit evidence and known identity limitations. Complete the legal/operational decisions and independently approve the record. |
| 17 | Inbox usability and desktop parity | B | Implemented / acceptance required | The inbox uses tenant-scoped stable cursor pagination in pages of 50 and protects unsaved drafts during route/history navigation, reload, conversation selection, filtering and search. Operational timestamps use the saved hotel timezone, and a release-bound desktop-parity acceptance record is implemented. The implementation and preview HTTP suite pass; run the supervised exercise against the approved release and retain independent approval. |
| 18 | Pilot, capacity, and release approval | B/C | In progress | The `0.2.0` Gate B candidate has bounded capacity, five-business-day pilot, incident and final-decision record validators with agreed targets. Retain the exact source-package and Ansible-install evidence, complete Gate A and Gate B prerequisites, run the probe and supervised exercises, resolve or contain findings, and retain separate hotel-owner and technical approval. Gate C remains dependent on milestones 13 and 14. |
| 18 | Pilot, capacity, and release approval | B/C | In progress | The `0.2.1` Gate B candidate has bounded capacity, five-business-day pilot, incident and final-decision record validators with agreed targets. Retain the exact source-package and Ansible-install evidence, complete Gate A and Gate B prerequisites, run the probe and supervised exercises, resolve or contain findings, and retain separate hotel-owner and technical approval. Gate C remains dependent on milestones 13 and 14. |
## Delivery sequence
@ -81,9 +81,9 @@ Milestones 13 (Guestline/Rezlynx) and 14 (payments) can progress as parallel pro
## Next actions
- [ ] Merge the Action removal and release-process update to the intended default branch; that resulting commit becomes the new package candidate.
- [ ] Confirm the intended release commit/version because remote `main` and the published `0.2.0` tag currently identify different histories; do not move or reuse the published tag.
- [x] Preserve the published `0.2.0` tag unchanged and use the clean `0.2.1` candidate line from `main`, excluding Milestone 19 application code.
- [ ] Create and checksum the approved source archive with `deploy/package_source.py`, add/select it in the GuestOps Ansible playbook, and retain the package and installation evidence.
- [ ] Create and archive the immutable `0.2.0` approval tag only after Gate B approval.
- [ ] Create and archive the immutable `0.2.1` approval tag only after Gate B approval.
- [ ] Deploy to the target Debian environment with persistent MongoDB and data-protection keys.
- [ ] Run and record backup, restore, restart, monitoring, and rollback exercises.
- [ ] Complete real Google mailbox acceptance without using production guest data.

View File

@ -2,7 +2,7 @@
A Linux-hosted hotel email workspace, developed separately from the Windows GuestOps application. **This migration now includes AI draft generation, staff-approved Gmail sending, reviewed OHIP reservation updates, NMI hosted invoices, controlled FAQ auto-replies and team onboarding. It is not yet a production-complete replacement.**
Current release-candidate version: **0.2.0**
Current release-candidate version: **0.2.1**
Project progress is tracked in the [milestone report](MILESTONES.md). User-visible changes and release limitations are recorded in the [release notes](RELEASE_NOTES.md).

View File

@ -1,8 +1,8 @@
# GuestOps Release Notes
## 0.2.0 — Gate B release candidate
## 0.2.1 — Gate B release candidate
This candidate freezes the implemented Gate B scope for controlled acceptance. It is not yet approved for live hotel operations and does not become a release until the exact commit is pushed, a checksummed source package and operational evidence are retained, the supervised pilot is approved and the immutable `0.2.0` tag is created.
This candidate freezes the implemented Gate B scope for controlled acceptance. It is not yet approved for live hotel operations and does not become a release until the exact commit is pushed, a checksummed source package and operational evidence are retained, the supervised pilot is approved and the immutable `0.2.1` tag is created.
### Promoted scope
@ -26,6 +26,10 @@ These capabilities still require their separately documented provider, host and
See [MILESTONES.md](MILESTONES.md) for the gate assessment, delivery sequence, and remaining work.
## 0.2.0 — Superseded candidate identifier
The published `0.2.0` tag is retained unchanged for auditability but is not the approved deployment candidate. It identifies a development-line commit that is not on the clean Gate B release branch. Do not package or deploy it; `0.2.1` supersedes it.
## 0.1.0 — 29 September 2026
The `0.1.0` tag identifies the initial GuestOps Web foundation. It is not approved for live hotel operations.
@ -41,4 +45,4 @@ The `0.1.0` tag identifies the initial GuestOps Web foundation. It is not approv
### Versioning
The foundation remains tagged `0.1.0`. The .NET projects and frontend package now share candidate version `0.2.0`; create that immutable tag only after the exact commit, checksummed artifacts and Gate B acceptance evidence have been approved.
The foundation remains tagged `0.1.0`. The .NET projects and frontend package now share candidate version `0.2.1`; create that immutable tag only after the exact commit, checksummed artifacts and Gate B acceptance evidence have been approved.

View File

@ -3,7 +3,7 @@
"system": "guestops-backup-recovery",
"evidenceId": "backup-restore",
"dataClassification": "synthetic-only",
"releaseVersion": "0.2.0",
"releaseVersion": "0.2.1",
"releaseCommit": "0000000000000000000000000000000000000000",
"releaseRecordSha256": "0000000000000000000000000000000000000000000000000000000000000000",
"archiveSha256": "0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -11,7 +11,7 @@ import re
from urllib.parse import urlparse
VERSION = "0.2.0"
VERSION = "0.2.1"
SCENARIOS = {
"encrypted-manual-backup",
"scheduled-backup",

View File

@ -2,7 +2,7 @@
"schemaVersion": 1,
"system": "guestops-debian-host",
"evidenceId": "debian-host",
"releaseVersion": "0.2.0",
"releaseVersion": "0.2.1",
"releaseCommit": "0000000000000000000000000000000000000000",
"releaseRecordSha256": "0000000000000000000000000000000000000000000000000000000000000000",
"archiveSha256": "0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -11,7 +11,7 @@ import re
from urllib.parse import urlparse
VERSION = "0.2.0"
VERSION = "0.2.1"
SYSTEMS = {
"guestops-debian-host": {
"evidenceId": "debian-host",

View File

@ -2,7 +2,7 @@
"schemaVersion": 1,
"system": "guestops-persistence",
"evidenceId": "persistence",
"releaseVersion": "0.2.0",
"releaseVersion": "0.2.1",
"releaseCommit": "0000000000000000000000000000000000000000",
"releaseRecordSha256": "0000000000000000000000000000000000000000000000000000000000000000",
"archiveSha256": "0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -34,7 +34,7 @@ def timestamp(value: object, field: str) -> dt.datetime:
def validate(record: object) -> None:
require(isinstance(record, dict), "Approval record must be a JSON object.")
require(record.get("schemaVersion") == 2, "Unsupported approval schema; Gate B 0.2.0 requires schemaVersion 2.")
require(record.get("schemaVersion") == 2, "Unsupported approval schema; Gate B 0.2.1 requires schemaVersion 2.")
gate = record.get("targetGate")
require(gate in ("B", "C"), "targetGate must be B or C.")
require(record.get("decision") == "approved", "Only an explicit approved decision passes validation.")

View File

@ -1,6 +1,6 @@
# Gate B automation, identity and privacy acceptance
Run these reviews against the exact `0.2.0` candidate on the accepted HTTPS sandbox. Keep guest data, staff addresses, provider agreements, screenshots and raw reports in the restricted evidence store. Repository records contain opaque references only.
Run these reviews against the exact `0.2.1` candidate on the accepted HTTPS sandbox. Keep guest data, staff addresses, provider agreements, screenshots and raw reports in the restricted evidence store. Repository records contain opaque references only.
## Knowledge, AI and FAQ automation

View File

@ -18,7 +18,7 @@ Before deployment, verify the archive against its source record:
```sh
python3 - <<'PY'
import hashlib, json, pathlib
r = json.load(open('GuestOps-0.2.0-COMMIT.source.json', encoding='utf-8'))
r = json.load(open('GuestOps-0.2.1-COMMIT.source.json', encoding='utf-8'))
p = pathlib.Path(r['artifact']['name'])
assert hashlib.sha256(p.read_bytes()).hexdigest() == r['artifact']['sha256']
print(r['commit'], r['version'], r['artifact'])
@ -29,17 +29,17 @@ After Ansible has built the commit-tagged images, the repository verifier perfor
```sh
python3 deploy/verify_release.py \
--archive GuestOps-0.2.0.tar.gz \
--archive GuestOps-0.2.1.tar.gz \
--record release-record.json \
--commit FULL_40_CHARACTER_SHA \
--version 0.2.0 \
--version 0.2.1 \
--output release-verification.json
python3 deploy/verify_release.py \
--archive GuestOps-0.2.0.tar.gz \
--archive GuestOps-0.2.1.tar.gz \
--record release-record.json \
--commit FULL_40_CHARACTER_SHA \
--version 0.2.0 \
--version 0.2.1 \
--verify-loaded-images \
--output loaded-image-verification.json
```

View File

@ -22,7 +22,7 @@ python3 deploy/capacity_probe.py \
unset CAPACITY_EMAIL CAPACITY_PASSWORD
```
For the `0.2.0` Gate B candidate, the approved targets are concurrency 10, p95 latency at or below 500 ms, error rate at or below 1%, and at least 25% CPU and memory headroom on the documented four-core, 7.6 GiB host. The generated result reports HTTP observations, not a pass/fail claim. Repeat after a warm-up, investigate every error, and retain independently captured host metrics with the report. Hash both retained files for the approval record. Do not point the probe at a live hotel or increase its built-in bounds to simulate a denial of service.
For the `0.2.1` Gate B candidate, the approved targets are concurrency 10, p95 latency at or below 500 ms, error rate at or below 1%, and at least 25% CPU and memory headroom on the documented four-core, 7.6 GiB host. The generated result reports HTTP observations, not a pass/fail claim. Repeat after a warm-up, investigate every error, and retain independently captured host metrics with the report. Hash both retained files for the approval record. Do not point the probe at a live hotel or increase its built-in bounds to simulate a denial of service.
## Five-business-day supervised pilot

View File

@ -19,7 +19,7 @@ def valid_record():
"system": "guestops-backup-recovery",
"evidenceId": "backup-restore",
"dataClassification": "synthetic-only",
"releaseVersion": "0.2.0",
"releaseVersion": "0.2.1",
"releaseCommit": COMMIT,
"releaseRecordSha256": RELEASE_SHA,
"archiveSha256": "c" * 64,

View File

@ -17,7 +17,7 @@ def common(system):
"schemaVersion": 1,
"system": system,
"evidenceId": acceptance.SYSTEMS[system]["evidenceId"],
"releaseVersion": "0.2.0",
"releaseVersion": "0.2.1",
"releaseCommit": COMMIT,
"releaseRecordSha256": RELEASE_SHA,
"archiveSha256": "c" * 64,

View File

@ -13,7 +13,7 @@ ROOT = Path(__file__).resolve().parents[1]
class ReleaseRecordTests(unittest.TestCase):
def test_writes_versions_checksum_and_immutable_image_ids(self):
with tempfile.TemporaryDirectory() as directory:
artifact = Path(directory) / "GuestOps-0.2.0.tar.gz"
artifact = Path(directory) / "GuestOps-0.2.1.tar.gz"
output = Path(directory) / "release-record.json"
artifact.write_bytes(b"reviewed image archive")
@ -40,7 +40,7 @@ class ReleaseRecordTests(unittest.TestCase):
)
record = json.loads(output.read_text(encoding="utf-8"))
self.assertEqual(record["version"], "0.2.0")
self.assertEqual(record["version"], "0.2.1")
self.assertEqual(record["commit"], "a" * 40)
self.assertEqual(record["images"]["api"]["id"], "sha256:api")
self.assertEqual(

View File

@ -21,12 +21,12 @@ WORKER_ID = "sha256:" + "c" * 64
class VerifyReleaseTests(unittest.TestCase):
def fixture(self, directory: str):
root = Path(directory)
archive = root / "GuestOps-0.2.0.tar.gz"
archive = root / "GuestOps-0.2.1.tar.gz"
record = root / "release-record.json"
archive.write_bytes(b"reviewed image archive")
release = {
"schemaVersion": 1,
"version": "0.2.0",
"version": "0.2.1",
"commit": COMMIT,
"artifact": {
"name": archive.name,
@ -44,7 +44,7 @@ class VerifyReleaseTests(unittest.TestCase):
def test_verifies_candidate_archive_record_and_record_checksum(self):
with tempfile.TemporaryDirectory() as directory:
archive, record, _ = self.fixture(directory)
result = verify_release.validate(archive, record, COMMIT, "0.2.0")
result = verify_release.validate(archive, record, COMMIT, "0.2.1")
self.assertTrue(result["verified"])
self.assertEqual(result["archive"]["sha256"], hashlib.sha256(archive.read_bytes()).hexdigest())
self.assertEqual(result["releaseRecord"]["sha256"], hashlib.sha256(record.read_bytes()).hexdigest())
@ -55,7 +55,7 @@ class VerifyReleaseTests(unittest.TestCase):
archive, record, _ = self.fixture(directory)
archive.write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "size does not match"):
verify_release.validate(archive, record, COMMIT, "0.2.0")
verify_release.validate(archive, record, COMMIT, "0.2.1")
def test_rejects_wrong_commit_version_reference_and_mutable_id(self):
cases = [
@ -70,17 +70,17 @@ class VerifyReleaseTests(unittest.TestCase):
mutate(release)
record.write_text(json.dumps(release), encoding="utf-8")
with self.assertRaisesRegex(ValueError, message):
verify_release.validate(archive, record, COMMIT, "0.2.0")
verify_release.validate(archive, record, COMMIT, "0.2.1")
def test_loaded_image_ids_must_match(self):
with tempfile.TemporaryDirectory() as directory:
archive, record, _ = self.fixture(directory)
with patch.object(verify_release, "loaded_image_id", side_effect=[API_ID, WORKER_ID]):
result = verify_release.validate(archive, record, COMMIT, "0.2.0", True)
result = verify_release.validate(archive, record, COMMIT, "0.2.1", True)
self.assertTrue(result["loadedImageIdsVerified"])
with patch.object(verify_release, "loaded_image_id", return_value="sha256:" + "d" * 64):
with self.assertRaisesRegex(ValueError, "Loaded api image ID"):
verify_release.validate(archive, record, COMMIT, "0.2.0", True)
verify_release.validate(archive, record, COMMIT, "0.2.1", True)
if __name__ == "__main__":

4
web/package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "guestops-web",
"version": "0.2.0",
"version": "0.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "guestops-web",
"version": "0.2.0",
"version": "0.2.1",
"dependencies": {
"lucide-react": "^0.577.0",
"react": "19.2.8",

View File

@ -1 +1 @@
{"name":"guestops-web","private":true,"version":"0.2.0","type":"module","scripts":{"dev":"vite --host 127.0.0.1","build":"tsc -b && vite build","check":"tsc -b"},"dependencies":{"react":"19.2.8","react-dom":"19.2.8","lucide-react":"^0.577.0"},"devDependencies":{"@types/react":"^19.2.0","@types/react-dom":"^19.2.0","@vitejs/plugin-react":"6.1.1","typescript":"~5.9.3","vite":"8.2.2"}}
{"name":"guestops-web","private":true,"version":"0.2.1","type":"module","scripts":{"dev":"vite --host 127.0.0.1","build":"tsc -b && vite build","check":"tsc -b"},"dependencies":{"react":"19.2.8","react-dom":"19.2.8","lucide-react":"^0.577.0"},"devDependencies":{"@types/react":"^19.2.0","@types/react-dom":"^19.2.0","@vitejs/plugin-react":"6.1.1","typescript":"~5.9.3","vite":"8.2.2"}}