Commit Graph

568 Commits

Author SHA1 Message Date
6cfd9135bb fix: resolve production log errors — profile, datasets, migration recovery, mapper validation, reencrypt
- AuthRepository: add missing save_user_dashboard_preference (PATCH
  /api/profile/preferences returned 500)
- DatasetItem.schema_name nullable — GET /api/datasets no longer 503s on
  Superset datasets with null schema
- migration: TaskLogger calls used extra= kwarg which raised TypeError and
  killed the composite-key recovery branch, leaving the target server
  unrepaired after failed dashboard imports (e.g. PR-0160); switched to
  metadata=
- maintenance settings: lazy-create singleton with IntegrityError race guard
  instead of permanent 404
- mapper upload-xlsx: structured 400 detail (expected vs actual columns);
  MapperTool shows inline error panel instead of toast; i18n ru/en
- connection_service: eager re-encryption of legacy plaintext passwords in
  get_connection; returns a copy so live config never holds plaintext
- check_target_schema: log exception type/repr/traceback (was empty error)
- scheduler/maintenance auto-end: skip empty-tick INFO logs; suppress HTTP
  framing for GET polling endpoints
- reencrypt script: repair broken merge artifact, JSON-serialize payload on
  save, fix failed-count arithmetic
2026-08-26 12:50:03 +03:00
8a3bba139e fix(database): auto-reset orphaned Alembic revisions 2026-08-25 16:06:14 +03:00
4b9619e229 refactor(deploy): standardize enterprise environment file 2026-08-25 15:30:10 +03:00
a2ede492b7 fix(database): reset any legacy Alembic revision 2026-08-25 15:21:42 +03:00
5bc1b62bd0 refactor: unify initialization and reset migration baseline 2026-08-25 11:42:31 +03:00
9fb37a7e9d fix(settings): normalize empty validation policy lists 2026-08-24 17:02:15 +03:00
c1222eb906 feat(scenarios): add SQL evidence and bounded transforms 2026-08-24 17:02:06 +03:00
0c895cf416 feat(logging): unify canonical task CoT events 2026-08-24 17:00:17 +03:00
ffa4d6a85b feat(scenarios): implement execution engine contracts 2026-08-21 16:15:40 +03:00
adbea9db18 fix(translate): fail-closed source handling, double insert, retry recount, scheduler races, LLM parse safety, and improved BI-analyst error UX 2026-08-20 17:34:39 +03:00
63a839e3b0 fix(translate): fail closed on missing source and silent run success
Stop preview/env fallbacks when a configured datasource is gone, skip the
duplicate final insert after streaming, and surface retry/scheduler/LLM
edge cases as FAILED instead of COMPLETED.
2026-08-20 15:37:16 +03:00
585a00c537 semantic-curation: fix anchors, metadata, and relations across backend + specs
- Replace legacy @PURPOSE with @BRIEF across 241 files
- Add missing [C:N] complexity tiers to function contracts in core modules
- Fix tombstone contracts: add @STATUS DEPRECATED to 5 deprecated anchors
- Resolve 7 unresolved @RELATION edges in executor.py (DictionaryManager, TranslationPreview, etc.)
- Fix flat hierarchical IDs in 4 test files (22 test functions)
- Fix invalid tags/relations in logger.py (@ADR, @CONSEQUENCES, DISABLED_BY)
- Rebuild semantic index: 9,576 contracts, 4,742 edges, 0 parse warnings
2026-08-20 11:45:15 +03:00
82a519a347 feat(scenarios): complete editor execution and analytics 2026-08-20 11:32:26 +03:00
e291ba757f fix(agent): full-catalog dashboard search and working LLM retry
- search_dashboards: call /api/dashboards with page_context=other and
  page_size=100 so the profile 'My Dashboards Only' filter can no longer
  hide the whole catalog; parse available_total/effective_profile_filter
  and report hidden-by-filter instead of a false 'no dashboards' answer
- prefetch_dashboards: same full-catalog context; fix dead code where
  data=resp.json() sat after return '' inside the error branch, making
  every 200 response raise NameError and the prefetch always return ''
- llm-status: ?force=1 bypasses the 30s health cache so the 'Retry now'
  button performs a fresh probe instead of re-reading the stale status;
  frontend keeps a single retry interval (previously stacked intervals
  decayed the countdown faster than 1/s and fired duplicate probes)
- tests: agent tool/prefetch, backend route bypass + force param,
  frontend retry/force coverage
2026-08-19 19:21:00 +03:00
615f3ccd25 fix: graceful fallback when Superset rejects changed_on_dttm filter during incremental sync 2026-08-19 17:38:35 +03:00
488a8f349b test(backend): raise coverage to 95%+ statements and branches (97.8%/95.0%)
- ~60 new/extended test files across api, core, plugins, services, schemas:
  routes, superset clients, task_manager, lineage, git, translate,
  dashboard-testing, load-testing, migration, llm_analysis, scheduler, ssl
- .coveragerc: enable branch coverage; exclude src/__tests__ (test files)
  and src/scripts (CLI/ops tools) from the denominator
- bug fixes found while testing:
  * settings: PUT /settings/reports registered under duplicated prefix
  * schemas/lineage: FleetReportDTO missing run_status (route always 500)
  * dashboard_testing/baseline_inheritance: visual entry read wrong field
  * superset_client/_databases: logger extra name shadowed LogRecord attr
  * routes/datasets: _yaml_string_paths recursion without yield from
  * translate/sql_generator: restore explicit-type timestamp contract
  * baseline_catalog: remove unreachable dashboard_id fallback
- conftest fixes: pytest_plugins to rootdir conftest (pytest 9), test
  filename collision, TMPDIR-safe integration fixtures
2026-08-19 17:14:32 +03:00
dd9df0fc5e feat(env-widget): dashboard stats widget with per-env counts, health probe and profile-filter reference info
- Replace the global env <select> in TopNavbar with an expandable
  EnvironmentStatsWidget showing per-env total/mine/published/drafts
  and health status (latency, unreachable), preserving env switching.
- Add GET /api/environments/stats: per-env counts (profile-actor matched)
  + lightweight health probe, gathered concurrently with an 8s probe
  timeout and a process-local TTL cache (30s, coalescing) so the full
  Superset dashboard catalog is not re-fetched on every dropdown open.
- Add available_total to GET /api/dashboards so grids can show how many
  dashboards exist when the profile-default filter hides everything.
- Share ProfileFilterBanner across the dashboards hub and validation
  task form: 'showing X of Y' reference info + explicit Show all /
  Restore filter actions.
- Russian plural forms for dashboard counts (pluralRu helper) and
  compact 'Опубл.' label; i18n keys en/ru.
- Ignore :memory:test_* SQLite test artifacts and drop them from the index.
- Tests: env stats endpoint (incl. caching), widget, model fallback,
  plural helper, api client, integration.
2026-08-19 14:40:14 +03:00
81ba94684f feat: sed-мутации датасетов, правки миграции и фиксы
- Dataset viewer: sed-замена (find→replace) по всем/выбранным/текущим датасетам
  с обязательным визуальным предпросмотром и целями (sql/metrics/yaml); сохранение
  и выбор именованных правил (sedRules store + SedRuleEditor).
- Migration: literal-замена в dataset YAML при переносе; рескан ID чартов/датасетов
  перед миграцией + метрика средней длительности синка (GET /migration/sync-stats);
  логическая группировка опций (маппинги БД, сервер изменений, rescan) под галочками.
- Fix: дашборды хаба скрывались дефолтным профиль-фильтром show_only_slug_dashboards=true
  (теперь false); мастер миграции не грузил дашборды предзаполненного источника;
  потеря терминального task_status из-за утечки pending-корутин в /ws/logs (панель
  результата не появлялась); горизонтальный скролл в MappingTable; чистая per-env
  ошибка в mapping coverage.
- Backend: record_sync_duration + alembic-миграция sync-duration; literal_replace модуль.
2026-08-19 10:29:28 +03:00
2a0f334717 chore: remove hardcoded Fernet key and client cert
- ENCRYPTION_KEY: smoke test generates a fresh key inline; templates use a placeholder
- drop RUSAL_ROOT.cer (client-specific public cert) + gitignore it
2026-08-18 12:32:06 +03:00
977f3d6d75 chore: checkpoint working tree onto master
Carried over from 042-dashboard-scenario-registry:
- dashboard/migration backend changes + tests (dataset_key_sync)
- specs updates; drop generated doxygen artifacts
- research notes, integration artifacts, session log
2026-08-18 12:20:42 +03:00
1dd78ca548 fix integration test failures 2026-08-17 16:09:03 +03:00
c32b7ef509 feat(translate): extend run metrics with observed flow stats; test hardening
- backend: aggregate cache_hits/observed_runs/source_records_read/eligible/
  translated/same_language_skipped/insert_rows_* preserving NULL for
  historical runs; drop legacy translate plugin module
- frontend: history page metric cards + RunOutcomeCompact per-run summary,
  totals with observed-scope notice; tabular numbers
- tests: fix banner date-format expectations, api-key env-scope fixture,
  rate-limiter cache pollution pinning, chart/candidates guards
- specs: sync dashboard-testing openapi contract
2026-08-13 08:23:43 +03:00
6336de9c24 fix(translate): correct preview responses and target DB selection 2026-08-11 12:34:00 +03:00
e7d33ce4c8 chore(db): drop orphaned dataset-review and connection_configs tables
Remove dead schema left behind by removed features:
- dataset-review family (dataset_review_sessions, dataset_profiles, and
  related children) from c3ad0afc — its non-cascading FKs broke environment
  deletion with ForeignKeyViolation
- connection_configs from 74e64622

Both are unreachable from the app (no models register them).
2026-08-11 10:55:52 +03:00
e52c5777ba feat(maintenance): fan out API starts to prod 2026-08-10 15:56:03 +03:00
9450559da5 feat(maintenance): improve event history and templates 2026-08-10 15:05:20 +03:00
7924ec5b10 fix(logs): reduce production log spam — agent llm-config polling, scheduler plumbing
- middleware: suppress structured REASON/REFLECT framing for high-frequency
  pollers (/api/agent/llm-config, /api/tasks/{id}, health/summary,
  session/activity, settings/consolidated); fixes tasks/{id} never matching
- agent: _fetch_llm_config treats 401/403 as terminal (no retry, log once),
  bounded backoff 5s/15s/60s on connect/timeout/5xx; langgraph_setup logs
  auth failure once per process
- scheduler: auto-end plumbing lines (executed/scan triggered) -> DEBUG
- thumbnail: Superset 4xx rejections logged at DEBUG instead of EXPLORE
2026-08-10 12:28:07 +03:00
db255ea4e6 feat(maintenance): ui/ux audit improvements for BI analyst persona
- read-only access to /maintenance for analysts (sidebar + hidden management)
- hub badge: message tooltip, link to events, accessible aria-label, localized end
- keep hub badge fresh via shared maintenance WS (init on dashboards page)
- events table: message column, auto-end indicator, localized statuses
- confirm dialog before starting maintenance with affected-dashboards summary
- surface load errors inline; localize store toasts
- auto-end discoverability hints in form and table
- form: multiple tables, end>start validation, timezone note
- status colors: active -> warning; completed tab dashboards expandable
- settings: timezone select, fieldset, localized units/aria labels
- backend: expose auto_end in event items, message in banner states
2026-08-10 12:04:43 +03:00
512e9223e3 feat(maintenance): configurable date format for banner timestamps 2026-08-10 11:22:48 +03:00
3421484347 chore: synchronize remaining workspace updates 2026-08-10 07:29:08 +03:00
1c577e8561 docs(semantics): normalize code contracts 2026-08-10 07:28:05 +03:00
093f7f600f fix(maintenance): harden banner lifecycle and guarded migrations
- Guard maintenance Alembic operations for create_all-only tables on clean DBs
- Add guarded verification_runs.fanout_plan_id backfill migration
- Improve maintenance banner rendering, chart management, orchestration, and API routes
- Expand assistant maintenance tool and edge-case coverage

Tests: cd backend && source .venv/bin/activate && python -m pytest -q tests/test_maintenance_api.py tests/test_maintenance_service.py tests/api/test_assistant_tool_maintenance.py tests/api/test_maintenance_routes_edge.py (77 passed)
2026-08-09 08:22:16 +03:00
066dfe3a35 fix(alembic): merge two parallel heads from o1p2q3r4s5t6
Runtime migrations failed with 'Multiple head revisions are present' because
037 T081 (p2q3r4s5t6u7 -> verification_runs.dashboard_id) and a concurrent
session-activity change (a1b2c3d4e5f7) both branched from o1p2q3r4s5t6.
The failed 'upgrade head' left dashboard_id unapplied, causing
'column verification_runs.dashboard_id does not exist' on
GET /verification/history.

Add a no-op merge revision (015281bd7759) collapsing both into a single head
so 'upgrade head' applies the verification_runs.dashboard_id column.

Verified: ScriptDirectory.get_heads() == ['015281bd7759'].
2026-08-07 16:33:30 +07:00
c9664dfabc fix(040/038): address code-review criticals (C1-C4, H1, M1, M3)
QA review of the 036-041 closure range returned FAIL with 3 criticals, all
confirmed. Fixes:

C1 - breaker dead: on_result=persist_batch is now wired into RunnerPool
  (breaker.record() fed per result); added test_breaker_abort_persists_partials
  proving CIRCUIT_BREAKER_ABORT reachability + partial persistence.
C2 - index-based result mapping corrupted data under concurrency: results now
  map by execution_id to their source item; test uses two distinct payloads
  and asserts chart->digest pairing (previously masked by identical fixtures).
C3 - double-acquire of the shared client semaphore (deadlock invariant):
  RunnerPool no longer manually acquires the client semaphore; capacity is
  enforced by worker count, the client bounds total concurrency.
C4 - duplicated ScenarioGraph.Vlm.Analyze region: outer region renamed
  ScenarioGraph.Vlm [TYPE Module].
H1 - _default_submit stub removed: analyze_screenshot requires submit=; no
  silent empty-findings fallback.
M1 - test_capture_dispatch.py region closed.
M3 - capture.py raw_sha256 bypass removed: digest always derived from real
  capture_bytes (no caller-supplied hash).

Verification: load_testing (77) + scenario (103) = 180 passed; ruff clean;
all region pairs balanced.
2026-08-07 16:18:29 +07:00
0a445fb170 feat(041): lineage frontend blast-radius + deprecation + opt-in rationale (T045-T048)
Close the 041 frontend/opt-in gaps found in the audit: no /lineage UI existed
and lineage_index stayed opt-in without documented rationale.

T045 - bind the existing Datasets.LineagePanel (T031) onto /datasets/[id]
  (blast-radius dependents + stale_index notice); deleted my transient
  duplicate to respect component reuse.
T046 - DatasetsLineageModel.markDeprecated()/recordMigration() + LineagePanel
  deprecation lifecycle section (grace window, successor, migration uuid);
  consumes existing api/lineage.ts + lineage.json i18n keys.
T047 - DatasetsLineageModel.loadFleetReport() for fan-out fleet report.
T048 - config_models.py: lineage_index_enabled default stays FALSE with an
  explicit rationale (post-sync Superset detail-call cost; flip after live
  indexer stability proof); consumers treat disabled index as empty read-model.

Verification: lineage + api vitest = 236 passed; vite build OK; eslint clean
for changed code (pre-existing ruff/require-each-key warnings untouched).
2026-08-07 15:27:24 +07:00
410afdf40e feat(037): verification pipeline automation + GET read-API (T080-T081)
Close the 037 pipeline-automation and read-API gaps found in the audit:
deploy/release hooks did not create VerificationRun, and GET endpoints for
history/detail were absent even though 039 UI and client call them.

T080 - _release_routes.py: create_release now fires best-effort
  _trigger_release_verification -> VerificationRun with trigger=release_create
  (metric+structure); verification scheduling failures never roll back the
  release transaction.
T081 - verification.py: add GET /verification/history (dashboard_id +
  environment_id filters, newest-first) and GET /verification/{run_id}
  (404 RUN_NOT_FOUND); reuse _record_to_response.
  - verification_run.py + alembic migration p2q3r4s5t6u7: nullable indexed
    dashboard_id populated from structure/visual/metric category_params.
  - verification_service.py: _derive_dashboard_id helper.

Verification: release routes (32) + verification API (8) + persistence (21)
= 53 passed; ruff clean for changed code (pre-existing RUF012/UP017 on old
lines left untouched).
2026-08-07 14:21:25 +07:00
d1e15904e1 feat(038): wire real VLM submit + real capture bytes (T057-T059)
Close the 038 MVP runtime gaps found in the audit: VLM analysis previously
returned empty findings with no provider call, and capture registered a
synthetic sha256 derived from run/step ids instead of real image bytes.

T057 - vlm.py: replace _default_submit stub with real submit_screenshot that
  resolves a multimodal provider via LLMProviderService (decrypted key,
  multimodal-required gate) and calls Plugin.Service.LLMClient.get_json_completion
  with the masked screenshot; analyze_screenshot is now async.
T058 - capture.py: dispatch_capture now REQUIRES real capture_bytes/masked_bytes
  and computes sha256 from the actual image bytes (synthetic hashes forbidden);
  scenario API accepts base64 capture/masked bytes.
T059 - test_scenario_vlm_e2e.py: capture -> VLM -> disposition end-to-end with
  real bytes (masked bytes reach the provider; digest matches sha256 of bytes).

Verification: tests/services/dashboard_testing/scenario/ = 103 passed, ruff clean
(existing B008 on pre-existing draft-pack route lines untouched).
2026-08-07 14:08:04 +07:00
a1b20bf2cf feat(040): wire RunnerPool into run_load_run — real load execution (T075-T079)
Close the 040 MVP runtime gap: run_load_run previously only slept through
RAMP->STEADY->DRAIN->COMPLETED with zero Superset requests. Now it actually
executes load-test chart queries through the 037 client.

- executor.py: 037-native adapter (execute_superset_chart via
  execute_dashboard_query_envelope; build_execution_items with stable ids)
- persistence.py: write_load_executions batched LoadExecution persistence
- plugin load_testing.py: run_load_run -> _resolve_execution_scope ->
  _run_bounded_pool (RunnerPool + shared client_registry.get_semaphore +
  CircuitBreaker -> circuit_breaker_abort)
- test_executor_runtime.py: 5 tests proving executor delegation, item
  determinism, real-execution persistence (2 items -> 2 LoadExecution rows,
  run -> COMPLETED), and lifecycle-only degradation on missing env

Verification: tests/services/load_testing/ = 76 passed, ruff clean.
2026-08-07 13:37:46 +07:00
c1c35e5855 fix(scenario): route draft storage through StorageService with legacy fallback, persist run marker for post-restart recovery 2026-08-07 10:24:32 +07:00
a5e69de5ea fix(scenario): complete save flow with auto-created repo, robust approval gate, idempotent auto-start 2026-08-07 01:56:36 +07:00
9cb5717a78 fix(agent): auto-start scenario chat, robust HITL resume, strict service auth
- frontend: fix auto-start on dashboards->/agent navigation (undefined params
  ReferenceError), route initial connect through ConnectionManager with auto-retry,
  reset runModel on objectId change and failed recovery
- agent: fix closure-over-loop-variable bug in _inject_env_id_into_tools (env now
  resolved from request-local ContextVar; idempotent wrapping), make
  execute_dashboard_result.result_key optional, resilient checkpoint resume with
  ToolMessage repair + direct-tool fallback, remove dead fast-path, consolidate
  tool_call parsing in _tool_resolver, context-safe ContextVar resets
- backend: llm-config gated by strict service-only auth (no user-JWT fallback),
  tighten idempotent run reuse (dashboard/env/intent match + 6h staleness),
  terminal event transitions run.status to COMPLETED/FAILED/CANCELLED,
  null-safe metric parsing in dashboard query model
- run.sh/docker-compose: require SERVICE_JWT (random per-run secret) instead of
  public default
2026-08-06 18:26:50 +07:00
b820b8b47c fix(maintenance): validate environment synchronously on start
start_maintenance accepted any environment_id, creating a stuck PENDING
event that never transitioned for unknown environments. Add synchronous
404 guard (mirrors preview_dashboards), inject config_manager via Depends,
and cover with a regression test proving no event row is created.

Also fix mock_task_manager to await broadcast_maintenance_event (AsyncMock),
aligning the fixture with the production route's awaited call.
2026-08-06 17:37:36 +07:00
6d19ecf81a fix: harden feature security and e2e integrations 2026-08-06 13:47:28 +07:00
170345af0a feat: 040 Superset/Testcontainers integration tests — spec complete (0 open)
- T066: test_dashboard_load_testing_superset.py — real Superset chart-data
  preserves source_response_hash + cache metadata (LOAD-FR-018/019).
- T067: test_load_testing_client_capacity.py — shared semaphore wiring,
  reserve slots, multiple runs, fairness.
- Ran against a real Apache Superset Testcontainers container
  (proxy-bypassed NO_PROXY for localhost).
- 040-dashboard-load-testing now 0 open / 74 done.
2026-08-05 22:30:31 +07:00
0d9f3e09f3 chore: close 041/040 tails — contract test, lifecycle test, fixtures, integration test
- 041 T038: 040 blast-radius consumer contract test (R6 pinned snapshot shape).
- 040 T003: materialize load-testing fixtures into frontend __fixtures__.
- 040 T029: run lifecycle test (ramp/steady/terminal immutability) + T038 aggregate-progress.
- 040 T051/T059: LoadResults + LoadComparison L2 UX tests.
- 040 T064: LoadModels integration test (profile -> gate -> run -> reconnect -> results).
- Check off verified tasks; remaining 040 open: e2e (T065), integration (T066/T067), a11y (T068).
2026-08-05 21:28:54 +07:00
9de74baa08 feat: dashboard testing suite — scenario UI, load testing, dataset lineage
- 039-dashboard-scenario-ui: agent workspace (WorkspaceModel, scenario
  views, parameters/baselines, artifact preview, HITL save/approval,
  evidence/VLM review, pipeline verification views) + contextVersion=2
  scenario intent + prototype.
- 040-dashboard-load-testing: capacity/matrix/profile, runner pool,
  timing, circuit breaker, PROD gate, cache/bounded/consistency,
  comparison/schedule, API + frontend + prototype.
- 041-dataset-lineage-blast-radius: usage index, severity/schema-diff,
  propagation, deprecation, fanout, API + frontend + prototype (R9
  labels/metrics/recreate).
- 036/037 amendments: dataset_updated trigger + fanout_plan_id, lineage
  deprecation gate, 040 read-model traceability.
- Includes pre-existing 042-rls-management-workspace and
  043-idm-account-integration work.
2026-08-05 18:15:30 +07:00
f9a15a0a7b feat(maintenance): opt-in auto-end at end_time via scheduler scan
Add auto_end flag to maintenance_events: when set with an end_time, a
60s APScheduler scan dispatches the end task automatically. The scan
survives restarts and is de-duped by task_id; end_time alone stays
informational. Includes alembic migration, route/schema wiring, Svelte
checkbox with validation, examples, and backend + frontend tests.
2026-08-04 16:38:20 +07:00
03684fd445 fix(maintenance): start idempotency returns real 409, not documented-but-200
The start endpoint declared 409 in OpenAPI responses but returned the
already_active idempotency hit as a plain 200. Now returns HTTP 409
Conflict with the declared MaintenanceAlreadyActiveResponse body
{maintenance_id, status: 'already_active'}.

Consumers updated to treat 409 already_active as idempotent success:
- bash example: 409 case in api_call
- python example: 409 branch in start_maintenance
- frontend form: info toast instead of error

New test: TestStartIdempotency verifies 409 + body + no new task
dispatched (naive datetimes to match SQLite tz-stripping).
2026-08-04 11:57:41 +07:00
d54f903660 feat(maintenance): settings (timezone, height), snapshot restore, UX polish
Settings:
- display_timezone defaults to Europe/Moscow everywhere (model, service
  fallbacks, settings form); migration flips untouched 'UTC' default row.
- New banner_height setting (1-200 grid units, NULL = auto): threaded from
  settings through start/rebuild flows into MARKDOWN insert/content updates;
  settings panel gains Auto/Manual radio + number input.

Banner removal:
- update_dashboard_layout returns the pre-mutation position_json; stored on
  maintenance_dashboard_banners.original_position_json at creation.
- Removal restores the snapshot verbatim when the current layout matches the
  deterministic replay of the insert (normalize + y-shift + banner keys);
  diverged layouts (user edits during maintenance) and legacy banners fall
  back to surgical removal. Fixes layout drift from y-shift and unreverted
  ROOT->TABS -> ROOT->GRID normalization.

UX:
- StartMaintenanceForm: recent-tables chips from event history, Enter-to-
  submit, schema.table format hint, inline task progress panel (status +
  progress bar + task-log link), templates section removed, Button atoms.
- Maintenance page: environment selector + env context init.
- Events table: Active/Completed tabs with count badges.
- Backend: plugins/services report monotonic progress via
  context.logger.progress (per-dashboard) for start/end/end-all.

Tests: 203 backend (matcher, restore/fallback, height, progress) + 3645
frontend pass; migration E2E-verified (upgrade/downgrade).
2026-08-04 11:08:11 +07:00
80dad15458 fix(maintenance): render banner as native MARKDOWN element, not a chart
Two defects fixed:
- ensure_banner_chart created an orphan 'Maintenance Banner' markdown chart
  (polluted Charts menu and dashboard exports). The banner is now a native
  MARKDOWN element in position_json; chart_id is a synthetic layout key.
- insert_banner_markdown_at_top blindly targeted GRID_ID; on ROOT->TABS
  dashboards (FI-0085) GRID_ID is orphaned and the banner never rendered.
  The layout is now normalized to ROOT->GRID_ID->[ROW-banner, ...] with
  recursive parents update, matching the proven-working manual example.

Review-driven hardening:
- liveness check verifies reachability from ROOT (children graph), so
  dashboards corrupted by the old bug self-heal on the next start.
- insert removes stale ROW-banner-*/MARKDOWN-banner-* keys (single banner).
- decision memory (@RATIONALE/@REJECTED) added to both modules.
- new ops script scripts/cleanup_maintenance_banner_charts.py (dry-run by
  default) deletes already-created bogus banner charts in prod.
2026-08-04 08:46:22 +07:00