Runbook — mobile contract v2 cutover
Обновлено: 09 Октябрь 2026
Версия: 2.4.0
Зеркало operational runbook из репозитория ask (documentation/technical/event-center/runbook-mobile-contract-v2.md). Контрактные таблицы wire: event-center-mobile-contract-v2.
Runbook: mobile contract v2 cutover (Event Center)
Operational cutover for Event Center mobile contract v2 on shared/prod-like stands.
Mirror in ask-doc: opisanie-api-ask-legarus/runbook-mobile-contract-v2.md.
Wire mapping / RR / capabilities: ask-doc opisanie-api-ask-legarus/event-center-mobile-contract-v2.md.
1. Order of operations
- Migrate schema + data (
0010_mobile_contract_v2_fields):bash source .venv/bin/activate && python manage.py migrate event_centerMigration adds nullableresolved_at/last_action_key, counters v2 columns (default0), and backfillsresolved_at=updated_atfor terminalaction_statusrows (completed/expired/cancelled). - Reconcile counters (defaults
0are not semantic until recalculated):bash source .venv/bin/activate && python manage.py reconcile_inbox_counters_v2Scope: users with any non-zero stored counter and/or non-archived personal recipients. Full active-user pass:--all. Single user:--user-id N. Dry-run:--dry-run. Alternative after search rebuild (all active users):bash source .venv/bin/activate && python manage.py rebuild_event_center_indexes --counters-only - Normalize legacy Inbox Open targets (optional but recommended after Open/entity
fixes — stale ACTION cards, wrong parent entity, unread INFO leftovers):
bash source .venv/bin/activate && python scripts/normalize_inbox_state.py # dry-run source .venv/bin/activate && python scripts/normalize_inbox_state.py --apply source .venv/bin/activate && python scripts/normalize_inbox_state.py --apply --user-id N # Steps include CRM leads, SD requests, KPI approvals, absences, workhours, constructor, counters.Among other steps, remaps: - WH:
servicedesk/request→workhour_record(Open → overtime card); - approvals:
finance/contract→approval_participant,projects/project→team_member_approval/baseline_approvalwhen payload hasapproval_entity_type/approval_entity_id. Then recalculates counters (unless--skip-counters). - Deploy code that exposes bootstrap
event_center_version ≥ 2and HTTP contract parsing. - Bootstrap version 2 — mobile clients read
event_center_versionfrom bootstrap //.well-known/ask-mobileand only then enable v2 Inbox behaviour.
Optional env (not required): ASK_EC_CONTRACT_DEFAULT=1 for stands that want an
explicit default when the header is absent (HTTP without header remains contract 1).
2. Flutter client
After bootstrap reports event_center_version ≥ 2, send:
X-ASK-EC-Contract: 2
on Event Center Inbox / actions / counters / search API calls.
Do not send contract 2 before version gate.
3. Web / HTTP vs WebSocket
| Channel | Contract |
|---|---|
HTTP without X-ASK-EC-Contract |
Contract 1 (as-is ORM / legacy shape) |
HTTP with X-ASK-EC-Contract: 2 |
Contract 2 (wire dual DTO) |
| WebSocket / sync gap recovery | Already dual wire (no header default=1) |
4. Handler waves (Pilot → Wave 2 → Wave 3)
Generic execute protocol (available_actions + POST …/actions/{key}/) ships
with v2. Handler registry is enabled by wave. Capability
generic_approval_execute=true means the protocol is ready — not that every
Matrix type is in-inbox.
| Wave | event_type (taxonomy) |
|---|---|
| Pilot | absence.approval_required, contract.approval_assigned |
| Wave 2 | task.duedate_transfer, project.team_approval_required, project.baseline_approval_required, kb.approval_required, kb.page_acknowledge |
| Wave 3 | servicedesk.kpi_approval_required, workhour.approval_required, workhour.tech_approval_required, workhour.client_approval_required, other ACTION types with approve/reject in EventTypeDefinition.actions |
Types outside the registered wave return available_actions=[] and execute
409 action_not_available — expected.
5. Rollback
- Flip bootstrap / feature so clients stay on contract 1 (no header / version gate below 2). Nullable v2 columns remain safe; no destructive DROP required.
- Counters can be re-reconciled after rollback if drift is suspected:
manage.py reconcile_inbox_counters_v2 --all.
6. Smoke (task 3.2)
Preferred (LiveServer / runserver-equivalent):
source .venv/bin/activate && python manage.py test event_center.tests.test_mobile_contract_runtime_smoke --settings=config.test_settings
Covers: EC enabled bootstrap version 2, EC disabled graceful bootstrap, HTTP
contract 1 vs 2 Inbox, non-pilot execute 409 without 500.
If LiveServer is unavailable in the environment, document and run a management
smoke entrypoint when present (smoke_mobile_contract_v2); otherwise use the
test module above against shared DB.
7. AutoResolve and last_action_key
System close / AutoResolve with overwrite clears claim:
last_action_key=NULL on successful terminal write. User execute paths that omit
overwrite leave the key unchanged. Do not treat NULL key after AutoResolve as
corruption.
8. Push payload
Push data keeps as-is identity fields and adds wire purpose / severity /
mobile_badge_total. Delivery must not mutate Inbox state. Prefer running
push-path smoke from event_center.tests.test_runtime_smoke / acceptance push
tests after cutover.
9. Optional index (not a blocker)
Composite index (user, action_status, resolution_reason) for heavy
status=auto_resolved search is optional. Existing
(user, action_status, …) indexes are enough for first ship. Add a separate
migration 0011_… only if staging shows slow auto_resolved filters; otherwise
skip.
10. Inbox Open targets (entity → URL)
Открыть on an ACTION card resolves via entity adapters / entity_links, not the
parent document. Canonical targets after product fixes:
| Domain | entity_module / entity_type |
Open |
|---|---|---|
| Overtime / WH workflow | servicedesk / workhour_record |
workhour_detail |
| Contract approval | finance / approval_participant |
contract_approval_decide |
| Project team approval | projects / team_member_approval |
team_approval_list |
| Baseline approval | projects / baseline_approval |
baseline_approval_list |
| Absence approval | personal / absence (UUID) |
absence_review |
| Supply specification | trading / specification |
specification_detail |
Legacy rows still pointing Open at parent request / contract / project are
rewritten by scripts/normalize_inbox_state.py (steps workhour_entity,
approval_entity).
11. Verification checklist
- [ ]
showmigrations event_center—0010_mobile_contract_v2_fieldsapplied - [ ]
reconcile_inbox_counters_v2completed; spot-checkmobile_badge_total === COUNTopen ACTION visible for a sample user - [ ] (If remapping legacy Open)
normalize_inbox_state.py --applydry-run reviewed - [ ] Bootstrap returns
event_center_version ≥ 2with EC enabled - [ ] Flutter sends
X-ASK-EC-Contract: 2only after version gate - [ ] Smoke 3.2 green
- [ ] Pilot execute works; Wave 2/3 types outside registry → empty actions / 409