Prepare clean 0.2.1 Gate B candidate
This commit is contained in:
parent
9c29bfcd86
commit
9971a63635
@ -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>
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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).
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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.")
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
4
web/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user