# Phase 2B Final Audit and Freeze

Audit date: 2026-09-27 (Asia/Dhaka). Repository: `F:/Projects/c-care`.
Starting HEAD: `2a84ded`; Phase 2B.5 was already present and uncommitted.
Baseline: 625 tests. No Phase 3 implementation, staging, commit or push.

## Scope and methodology

Reviewed Customer numbering/identity/lifecycle; addresses and contacts; physical
Device identity and policy; IMEI/serial reservation; purchase review and warranty;
Customer ownership and permanent company affinity; operational queries; all eight
Phase 2B Admin registrations; migrations; production settings and security hygiene.

Read actual models, services, lock helpers, admin forms/actions, queries, migrations
and existing tests. Compared frozen source against Phase 2A checkpoint `bad910e`.
Captured test/migration hashes before edits. Added adversarial integration tests,
real Admin HTTP requests, forced rollback snapshots, raw-ORM probes and PostgreSQL
separate-connection races that verify actual lock waits. Inspected installed
PostgreSQL constraints/indexes and the migration graph. Ran focused reproduction,
audit, deployment, schema and complete regression commands.

The audit is bounded by reviewed source and exercised schedules. It is not a
formal proof of deadlock freedom, a production penetration test, a forensic scan
of every Git object, or a verification of deployment infrastructure.

## Findings and severity

| ID | Severity | Finding | Resolution |
| --- | --- | --- | --- |
| B-01 | MEDIUM | A stale Device admin page could submit an explicit identifier correction after another correction and replace the newer current identifier. Existing stale tests exercised no-op saves, not this operation. | FIXED. Two adversarial tests failed before remediation and passed afterward. Signed per-Device identifier snapshots plus an under-lock slot revision precondition now reject stale, absent or tampered revisions. |
| B-02 | LOW | Production deployment checks retain HSTS subdomain/preload warnings W005/W021. | Accepted existing deployment limitation. Settings unchanged; operators must evaluate actual subdomains/preload readiness. |
| B-03 | INFO | Raw/bulk writes can bypass normalization, immutable ownership, lifecycle checks and permanent company affinity. | Documented boundary, explicitly demonstrated by a raw affinity probe. Supported application paths use locked services/validated saves. PostgreSQL constraints remain effective. |
| B-04 | INFO | Phase 2B domain services do not authorize callers. Global identity/history queries and native Admin are not tenant-scoped end-user interfaces. | Explicitly documented; no authorization adapters, grants, or inferred business scopes introduced. |
| B-06 | LOW | Purchase evidence Admin fetched each displayed reviewer separately: four rows required five queries. | FIXED with a reviewer select_related join; a measured regression now requires one query. |
| B-05 | INFO | Customer/address/contact ORM deletion and full-state model saves have broader semantics than service/Admin operations. | Existing documented contract retained: unreferenced Customer/details may be deleted by trusted ORM callers; full saves are explicit whole-state writes, not stale-safe patches. Use services or appropriate update_fields for stale mutations. Registry/evidence/ownership normal deletion is blocked. |

No CRITICAL or HIGH finding identified. B-01 is the only demonstrated MEDIUM
finding and is resolved. No unresolved CRITICAL/HIGH/MEDIUM remains. The final full regression gate
passed with 657 tests, 0 failures and 0 skipped.

### B-01 reproduction and minimal fix

1. GET a current Device change page with identifier X.
2. Another service call corrects X to Y.
3. POST an explicit correction from the old page to Z.
4. Before the fix, Z became current; after the fix, Y remains current and no Z row
   is inserted. Removing the revision is rejected rather than bypassing protection.

`DeviceForm` signs Device UUID plus active slot row UUID/update timestamps. No
physical identifier values are placed in the token. It validates the snapshot on
POST. The snapshot uses the same prefetched rows that the page displays; an
additional regression reproduced and fixed a GET-time race where a second query
could sign a newer, unseen identifier. Admin supplies the selected slot revision to `replace_device_identifier`,
which compares fresh state after locking Device and its identifier rows. This
closes the interval between form validation and mutation. Row timestamps also
reject X -> Y -> X restoration even when X's original identifier row is reused.
Ordinary no-op Device saves remain nonmutating and do not require a revision.

The service's optional keyword is `expected_identifier_revision=_UNSET`. Omission
retains intentional serialized correction semantics for existing service callers.
Explicit `None` expects an empty slot; otherwise supply `[str(row.pk),
row.updated_at.isoformat()]` from the reviewed slot. Admin always supplies it for
corrections. No schema or migration change was needed.

One pre-existing test, `DeviceAdminTests.test_admin_correction_preserves_history_and_duplicate_failure_rolls_back`,
now obtains and submits the new token for its two legitimate correction POSTs.
Its assertions are unchanged. This is the narrowly necessary compatibility change
for the reproduced defect; 624 other baseline tests are untouched. The 61 Phase
2B.5 tests and every historical migration are byte-for-byte unchanged from the
audit-start hash snapshot. Thirty-two audit tests are additive.

## Customer integrity and numbering

Company and customer number are immutable in supported model/service/Admin paths.
Creation locks Company before allocating its persistent company sequence; failure
rolls back allocation and insertion. Database uniqueness is `(company, number)`;
format/type/name constraints and positive sequence checks also exist. Concurrent
same-company creation serializes; different companies can allocate independently.
Committed deletion does not decrement the counter or reuse the number.

Audit probes set an existing counter behind allocated numbers and beyond capacity.
Creation fails closed without duplicate insertion or unintended counter advance.
There is no silent counter repair or new numbering scheme. A privileged writer
that deletes/reset counters after deleting identities is outside this guarantee.
Customer metadata services refetch under locks and patch only supplied fields.
Creating/reactivating requires active Company; metadata correction remains allowed
when inactive as documented. Company deactivation filters operational queries
without rewriting Customer flags. Company changes through forged stale objects do
not change persisted company context used by ownership services.

## Address/contact integrity

Customer ownership and contact type are immutable through validated writes.
One active primary address per Customer and one active primary contact per
Customer/type are partial unique constraints; primary implies active. Contact
active normalized values are unique per Customer/type. MOBILE/EMAIL/OTHER were
independently probed for normalized duplicates and customer isolation. PHONE and
WHATSAPP remain existing supported types with the existing normalization tests.
Email local-part case is preserved; domain case is normalized. No new universal
email/phone identity rule was imposed.

Primary switching serializes on Customer and rolls back the previous primary on
failure. Concurrent active-primary address creation leaves two records and one
primary. Lifecycle clears primary on deactivation and does not fabricate a primary
on reactivation. Customer/Company deactivation preserves detail rows but hides
operational results. Quick Customer phone/email fields remain independent.
Trusted ORM deletion of details is deliberately permitted; Admin deletion is not.

## Device and identifier integrity

Device catalog identity is immutable; variant/model compatibility, active catalog
prerequisites and current identification policy are checked by locked services.
UNCONFIGURED rejects registration. REQUIRED/OPTIONAL/NOT_APPLICABLE are interpreted
without redesign. Catalog/policy and reactivation races remain covered by the
unchanged registry suite. Device correction preserves inactive Device state.

IMEI normalization accepts cosmetic spaces/hyphens but produces 15 ASCII digits
with Luhn validation. PostgreSQL reserves normalized IMEIs across IMEI1 and IMEI2,
including inactive history. Serial normalization/reservation and the shared global
identity key prevent numeric serial/IMEI ambiguity. Correction retires rather
than deletes identity, and restoration reuses the same reserved row/slot. Registry
rollback and competing correction/registration tests pass in the full gate.

Exact identifier lookup deliberately remains global and resolves historical
identifiers and inactive Devices. It is not a company-scoped search or an access
decision. Device UUID remains the anchor across ownership and identifier changes.

## Purchase evidence and warranty

Purchase evidence is one protected record per Device, with coherent review state,
reviewer and timestamp enforced structurally. Review requires a freshly active
persisted User; stale review revisions reject. Material fact changes reset review
and retire only purchase-evidence-dependent active coverage. Existing real races
exercise verify/edit in both orders and reviewer deactivation.

Warranty source/date ordering, one active coverage and nonempty manual justification
are database protected; meaningful trimmed justification, active Device and verified
purchase prerequisites are application rules. Replacement retains old records and
rolls back on insertion failure. Stale replacement/clear preconditions reject.
Manufacturer/manual coverage remain unchanged by purchase edit/rejection.

Audit explicitly checks day-before/start/middle/end/day-after coverage boundaries:
`start <= on_date <= end`. This is recorded coverage only, never claim approval.
Ownership assignment/transfer/end leave all purchase and warranty columns intact,
including reviewer/status and timestamps. No automatic Customer is inferred.

## Ownership, affinity and lifecycle

OWNER is the only relationship type. Current is `ended_at IS NULL`; no cached
Device owner/company or Customer count exists. Database partial uniqueness protects
one current owner; GiST range exclusion protects historical and open intervals.
Raw overlapping concurrent inserts were tested with an actual PostgreSQL wait.
Adjacent closed/current ranges succeed; overlap fails. Zero-length closed ranges
remain deliberately supported and covered by baseline tests.

Services append to a locked timeline; naive/future timestamps and backdated
contradictions reject. A -> B -> A retains three periods. Closure/transfer facts
and closed history are immutable through supported paths. Protected Customer and
Device FKs prevent cascaded loss. Permanent company affinity derives from all
OWNER history, including after closure; stale in-memory company forgery, transfer,
Admin and concurrent cross-company attempts reject.

Inactive Customer/Company/Device preserves recorded ownership. New assignment or
transfer requires an active target Customer, Company and Device. Explicit closure
is allowed while inactive. Reactivation creates no relationships; catalog lifecycle
does not rewrite ownership. Identifier correction and ownership transfer serialize
without mutating each other's records. Future service presenter/requester actors
remain distinct; no ServiceCase customer assumption or implementation was added.

## Cross-company isolation and authorization

The additive A1/A2/B1/B2 matrix assigns separate Devices to four Customers, checks
company/customer queries and searches, and attempts every cross-company transfer.
Operational queries do not leak foreign-company rows or duplicate results. Detail
queries are separately customer-scoped. The extra A3 setup Customer is deliberately
unowned and does not create a spurious Device result.

Global identifier and recorded-history APIs have intentional broader semantics.
Services enforce domain integrity, not caller authorization. Staff, Groups and
direct Django permissions do not create Customer/Device business scope; the frozen
engine continues to reject unsupported domain targets. Native Admin model
permissions grant administrative access and must be restricted to trusted staff.
Future end-user entry points must authorize separately.

## Admin and stale-state matrix

| Surface | Protection/evidence |
| --- | --- |
| Customer lifecycle | readonly protected fields; service patch/refetch; existing stale HTTP/concurrency tests |
| Primary address/contact | readonly primary/activity/owner; explicit service actions; stale forms cannot restore primary/lifecycle |
| Device lifecycle | no raw save; readonly lifecycle/catalog; stale no-op forms do not write |
| Identifier correction | B-01 signed revision plus locked slot precondition; fresh/stale/tampered/absent tokens and post-validation interleaving tested |
| Evidence | signed revision and locked optimistic update/review checks; material edit invalidates review |
| Warranty | immutable history and expected current UUID for replacement/clear |
| Ownership | readonly facts/history; signed revision and expected current UUID for end/transfer |

All eight model Admin POST surfaces reject missing CSRF under a CSRF-enforcing
client. Existing view-only/delete/forged-field tests remain in the regression
suite. Lifecycle actions delegate to supported services. No new bulk/raw save
path or deletion action was introduced. Explicit lifecycle/primary actions apply
to current state; they are not historical-page compare-and-swap operations.

## Transactions and actual lock order

| Subsystem | Order |
| --- | --- |
| Customer create/update/lifecycle | Company UPDATE -> sequence (creation) or Customer UPDATE; validated save reenters held locks |
| Customer details | Company SHARE -> Customer UPDATE -> detail/primary peers UPDATE |
| Device registry/correction/lifecycle | Brand SHARE -> Category SHARE -> ProductModel SHARE -> optional Variant SHARE -> existing Device UPDATE -> identifier rows; registration inserts Device under catalog locks |
| Purchase edit | Device UPDATE -> purchase evidence UPDATE -> dependent coverage UPDATE if retired |
| Purchase review | reviewer User SHARE -> Device UPDATE -> evidence UPDATE -> dependent coverage UPDATE |
| Warranty | Device UPDATE -> purchase evidence UPDATE when required -> current coverage UPDATE |
| Ownership assign/transfer | target Company SHARE -> target Customer UPDATE -> Device UPDATE -> current ownership UPDATE; history reads under Device coordination |
| Ownership end | Device UPDATE -> current ownership UPDATE |

No new concrete supported-path lock inversion was demonstrated. No formal
arbitrary-composition deadlock guarantee is claimed. External callers must retain
ordering and avoid pre-acquiring conflicting locks around mixed operations.
Deactivation winning first causes incompatible new operational writes to reject;
ownership winning first permits later deactivation while preserving recorded facts.

Eight added PostgreSQL races cover transfer/correction in both orders, stale Admin
correction after an uncommitted correction, simultaneous primary address creation,
normalized EMAIL duplicates, raw temporal overlap, and manufacturer coverage versus
purchase edit in both orders. Existing races cover numbering, primary contacts,
policy/catalog transitions, identity reservations, device lifecycle/correction,
purchase review/coverage, and ownership lifecycle/affinity/backdating.

The additive rollback matrix snapshots all involved table columns, forces an
outer transaction failure after customer creation, primary switch, registration,
identifier correction, purchase verify/edit, warranty replacement, ownership
transfer and ending, and compares the complete restored state. Existing tests
also inject internal failures, including owner assignment and transfer insertion.

## Database versus application guarantees

| Invariant | PostgreSQL | Application/service |
| --- | --- | --- |
| Customer number | company-number uniqueness, format, positive sequence | allocation order, immutable number/company, no reuse through supported allocator |
| Address primary | conditional unique Customer; primary implies active | atomic switching and operational parent checks |
| Contact | conditional primary/type and normalized-value uniqueness | actual normalization, type/owner immutability, operational checks |
| Device catalog | foreign keys | model/variant compatibility, immutable catalog identity, policy/activity checks |
| Identifiers | active slot unique; permanent cross-slot IMEI/serial/key reservation; IMEI canonical shape | Luhn, serial canonicalization/key derivation, policy, immutable history |
| Purchase evidence | one-to-one Device; valid/coherent review metadata | reviewer active, future-date rejection, material-edit invalidation, revision checks |
| Warranty | active uniqueness, source/date checks, nonempty override reference/note | meaningful trimmed justification, evidence dependency, immutable replacement history |
| Ownership | OWNER type, end >= start, one current owner, GiST exclusion, FKs | permanent company affinity, active targets, append-only chronology, immutable facts/history |

Installed `pg_constraint`/`pg_indexes` definitions were inspected, rather than
assuming model declarations were applied. `QuerySet.update`, bulk creation/update,
raw SQL and private persistence helpers are trusted internal escape hatches, not
public safe-write APIs. An audit raw insert deliberately creates foreign-company
ownership after closure to prove affinity is application-only. It cannot bypass
the actual uniqueness/exclusion constraints. No triggers or broad redesign added.

## Queries and performance

Company/customer operational filters are lazy SQL predicates evaluated on current
flags; already materialized objects remain snapshots. Customer search and a list
of four owned Devices including their display strings each execute one query.
The purchase Admin reviewer regression measured five queries before B-06 and one
after its single-join fix, for four evidence rows. Baseline history tests traverse customer/company and device displays in one query.
Ordering uses stable UUID tie-breaks. Device Admin joins/prefetches display data;
ownership lists and choices join customer/company and catalog display relations.
No speculative optimization or global-lookup tenant filtering was introduced.

## Database and migrations

Thirteen project migrations are present. The graph has no conflicts, all shown
migrations are applied, and drift checks detect no changes. `btree_gist` 1.8 is
installed. `devices.0003_customer_device_relationship` remains unchanged and
contains the extension operation before the exclusion-constrained model creation.
Focused and full tests create and migrate a fresh isolated PostgreSQL database;
the developer database was not reset. No audit migration is needed.

Git migration history shows no modifications to historical project migrations.
Audit-start hashes additionally confirm all migration files, including the
uncommitted devices.0003, unchanged. Django migration status stores no content
checksum and cannot prove historical applied bytes beyond repository evidence.
Frozen Phase 1/2A source directories have no diff from `bad910e` through this audit.

## Production and security hygiene

Development checks pass. Production deployment checks run with a temporary
process-only generated secret and synthetic host/origin, without printing secrets
or changing settings. Only W005/W021 remain. Application configuration was checked;
actual TLS/proxy behavior, credentials, backups, isolation and infrastructure were
not verified and are not certified by this freeze.

A bounded tracked-tree/new-file scan covered 140 files before adding this report;
the final repeat covered all 141 tracked/new files with no candidates.
No credential-pattern, environment-file, database-dump, log, command-output or
temporary-artifact candidate was found. Exact local secret comparisons were made
without printing values; matches were inspected as generic schema/parameter names
and synthetic test fixture terminology, not embedded connection credentials. No
real Customer/IMEI/serial/invoice fixtures were added; audit identifiers are synthetic.
`.env` remains ignored/untracked. This is not an exhaustive historical secret scan.

## Files and verification record

Audit-specific edits: `apps/devices/admin.py`, `apps/devices/services.py`,
`apps/devices/evidence_admin.py`, one token
setup adjustment in `apps/devices/tests.py`, `docs/DEVICES.md`, plus new
`tests/test_phase2b_audit.py` and this report. Pre-existing uncommitted Phase 2B.5
files remain available for review; they are not attributed to this audit.

| Check | Result |
| --- | --- |
| Two B-01 tests before fix | 2 failed, confirming overwrite and missing-token bypass |
| Same two tests after fix | 2 passed |
| Focused audit module (initial) | 30 passed, 0 failed, 0 skipped; 16.934 seconds |
| GET-time snapshot refinement before fix | 1 failed; revision differed from displayed identifiers |
| Full regression before GET-time refinement | 655 passed, 0 failed, 0 skipped; 280.115 seconds |
| check | No issues |
| makemigrations --check | No changes detected |
| migrate / showmigrations | No pending migrations; all applied |
| migration graph / installed constraints | No conflicts; expected constraints/indexes and btree_gist present |
| production check --deploy | Exit 0; W005/W021 only |
| Focused audit after GET refinement | 31 passed, 0 failed, 0 skipped; 15.116 seconds |
| B-06 query-count reproduction before fix | 1 failed: 5 queries instead of 1 |
| Focused audit with all fixes | 32 passed, 0 failed, 0 skipped; 21.735 seconds |
| Full regression before reviewer-join fix | 656 passed, 0 failed, 0 skipped; 295.487 seconds |
| Final full regression with all fixes | **657 passed, 0 failed, 0 skipped; 284.803 seconds** |
| Git | Status/diff/stat reviewed; diff --check clean; nothing staged, committed or pushed |

## Freeze decision

Baseline tests: **625**. Audit tests added: **32**. Total/passed: **657**.
Failed: **0**. Skipped: **0**. All baseline assertions are retained; 624 baseline
tests are unchanged and one has only the documented required token setup added.
All existing PostgreSQL concurrency tests and eight additive races passed.

The full regression, migration graph, fresh-schema creation, isolation, ownership
history/temporal integrity, identifier reservation, evidence/warranty integrity,
Admin protections and secret/artifact checks satisfy the freeze gate. The resolved
MEDIUM and LOW defects have failing reproductions and passing regressions. Remaining
LOW/INFO findings are documented acceptable operating boundaries. No unresolved
CRITICAL/HIGH/MEDIUM finding remains. Application foundation verified; production
infrastructure remains unverified. No Phase 3 work was performed.

**PHASE 2B VERIFIED — READY TO FREEZE**
