KeyNova Sentinel — PMS Phase 1: state of the platform
Round: sentinel-2026-08-06-09-47 · TEST · freshly onboarded property (138 rooms, 3 types)
Build: frontdesk 07f54d5, pod-verified before launch · --suite all --onboard --strict --measured-on
Read this first
This is the third round. The first two could not be trusted, for reasons that are themselves findings.
- Round 1 (
05-46) ran on a half-seeded fixture..env.testheld a stale SUPER_ADMIN credential, and the onboard script consumed the failed login behindif (sa?.accessToken)with noelse— so it printed a clean success block and carried on. Tenants never got ENTERPRISE and no super-admin existed. Specs failed at setup withCannot assign role 'SUPER_ADMIN', which produced Accountant 0/3 and much of Front Office 1/14 on nothing real. - Round 2 was killed as compromised: 11 spec commits landed while it ran, so its isolated-retry pass re-read rewritten specs and its flip detection became meaningless.
- Round 3 ran on a frozen tree, a verified credential, a seeded group, and a pod-confirmed build. It is the first round that measures the platform rather than the harness.
Headline
| Round 1 | Round 3 | |
|---|---|---|
| Staff journeys GREEN | 12 / 53 | 14 / 53 |
| Flows verifiable | 135 / 141 (96%) | 135 / 141 (96%) |
| Flow pass-rate | 63% | 62% |
| Isolated-retry flips | 4 | 5 of 56 (9%) |
| Evidence screens | 88 | 89 |
| Element review | 81 owed | 89 owed (7 verdicts rejected as stale-build) |
By persona
| Persona | GREEN | |
|---|---|---|
| Room Attendant | 1/1 | ✅ |
| Housekeeping Supervisor | 4/6 | strongest real coverage |
| Front Office Agent | 3/14 | up from 1/14 — round 1 understated it |
| Duty Manager | 2/7 | |
| Night Auditor | 2/7 | |
| Cashier | 1/6 | |
| Reservations Agent | 1/9 | |
| Accountant | 0/3 | ⚠ still zero — not only the fixture |
The 126 L1 failures are not 126 defects
Classified by reading every failure message, not by matching substrings:
| Count | Bucket | What it means |
|---|---|---|
| 57 | Cascade | A scenario failing because an earlier one did. pos-folio-charge alone is 6 failures from ONE root: beforeAll did not complete the check-in. |
| 31 | Env room contention | ROOM_ALREADY_ASSIGNED on a shared/contended property |
| 25 | Coverage gap — still asserts nothing | Beyond the 17 fixed this session |
| 13 | New gates firing | The de-vacuum work catching conditions that silently passed before |
| 4 | Out-of-scope channel | No ResAvenue connection on a fresh tenant, by design |
After collapsing cascades, 15 distinct signals remain — and five of those are the same
sentence: "check-in did not surface an OPEN folio" (folio-quantity-reconciliation,
folio-sharer-split-single-buyer, folio-split-category-complete, open-folio-resolve,
folio-transfer-target-number). One root cause presenting five times.
Counting failures instead of causes inflates the defect count roughly fourfold. That error was made twice during this programme and corrected both times.
Why the red count barely moved (45 → 46)
Seventeen previously-silent skips now fail loudly, so more reds were expected. The count
stayed flat because 13 of the new gates fired — in pms-j-night-audit (3),
brute-force-lockout (3), cash-refund (2), folio-discount (2), reservation-amend,
folio-void-creditnote, tenant-isolation — roughly offsetting improvements elsewhere.
The platform did not get worse. The instrument got sharper. Those 13 conditions were passing silently in round 1.
Confirmed defects
| Card | Severity | Finding |
|---|---|---|
| AB#11264 | P1 | Night audit: the pre-audit report says canProceed, and start() refuses with 400 — in the same run, on the same state. The wizard tells the operator the opposite of what will happen, on an irreversible financial operation. |
| AB#11267 | P1 | inspectRoom is a cross-tenant WRITE — no tenant predicate, never calls getRoom, while all three siblings in the same file do scope. A housekeeping user of tenant A can flip tenant B's room to INSPECTED. Also swallows a failed cross-service room update and returns 201. |
| AB#11271 | P2 | MvHkStaffProductivity.reworkCount is written only as the literal 0 and printed as "Rework Count" on a shipped, exportable operator report. Permanently zero for every attendant on every property. |
Fixed and deployed this session: AB#11250 · 11251 · 11252 · 11254 · 11255 · 11256 · 11269 (bad dates → 400 not 500, mutation-verified) · 11268 (the KPI counters that had no writer — unblocked AB#11248's acceptance criteria).
Closed as not-reproducible: AB#11253. The stay-shorten credit posts, persists and reads back correctly — 26,250 → 10,500, conserving to the rupee. The original finding was a fixture artifact: every CHECKED_IN stay on TEST was historical, so a correct implementation credits zero, which is indistinguishable from a credit that failed.
The finding that matters most
The certification could not fail for much of what it claimed to certify.
Seventeen cases across nine suites — every one a money path — were fixed this session. None was a missing assertion:
| Failure mode | Count |
|---|---|
| A skip disarms a downstream money invariant | 8 |
| The case names the bug in a console message, then passes | 3 |
| One observable, forgiving cause assumed (404 / bare catch) | 2 |
| Tautology — the guard skips on exactly what it asserts | 1 |
| Assertion too weak to fail (one-sided balance check) | 1 |
| Value computed, then discarded | 1 |
| Negative control skippable | 1 |
Three examples worth stating in full:
- TC-702 detected
currentBusinessDate still null after audit, wroteBUG-CANDIDATEto a console nobody reads, and returned green. - TC-694b asserted the GST split only when tax was already present — a room night
posted with zero CGST and zero SGST, on a registered supplier, took the
elsebranch and passed. - TC-708 asserted
balanceAfter <= balanceBefore + TOLand called that "rollback voids the charges". A rollback that did nothing satisfies it.
The platform's most consequential nightly financial operation carried the most assertion-free certification.
Evidence
89 screens captured, 89 element-reviews owed. Seven previously-recorded verdicts were
rejected as stale — recorded against an older build — which is the --measured-on
freshness check doing its job.
All 18 blank frames from round 1 are explained, and none was a broken capture: 7 were API-only stages that never declared themselves, and the rest fired a guard or a skip before navigating. A spec that stops before navigating has nothing to photograph; the blank frame was the honest signal.
What is not yet known
- Accountant 0/3. Round 1's zero was largely a fixture artifact; round 3's is not, and its cause is not yet established.
- The five "check-in did not surface an OPEN folio" specs. The helper that swallowed the server's refusal was fixed this session, so the next round will name the actual cause instead of the symptom.
- 25 cases still assert nothing — the sweep of 17 was not the whole population.
unregistered-supplier-zero-gststill fails withCannot assign role 'SUPER_ADMIN'even after the credential fix, because the spec mints a SUPER_ADMIN using its tenant-admin token. A spec defect, not the fixture.
Verdict
Not ready to certify, and the reason is measurement before product.
Fourteen of 53 staff journeys are GREEN. But the round's most important output is not that number — it is that a large share of what was previously counted as passing could not have failed. Until the remaining 25 vacuous cases are fixed and the element review is done, a higher GREEN count would not mean more confidence.
The correct next step is not to chase the score. It is to finish making the tests capable of failing, then measure again.