feat: add public h5 dashboard and publish traceability

This commit is contained in:
Wyndham ARR
2026-08-03 13:04:41 +08:00
parent 7e7470821b
commit 2107f00e32
29 changed files with 1047 additions and 71 deletions

View File

@@ -0,0 +1,43 @@
# ARR Job Trace Semantics for Internet Deployment
## Question
Whether the ARR task trace is understandable when ARR is deployed on a server reachable through the internet, rather
than being interpreted as if processing occurred on the operator's computer.
## Findings
- The current `artifact_callback` path is owned by ARR's fixed processor. It does not create a remote Agent/MCP run,
so `remote_run_id=null` is expected and is not a failure.
- Persisted delivery keys such as `local-arrjob-...-a1` are delivery/idempotency identities. They are retained for
compatibility and are not a claim about the user's machine or the server's geographic location.
- A successful job with `current_stage=downstream` and `DOWNSTREAM_EVENT_QUEUED` has already committed the Finance
version. `publish_status=pending` means the durable outbox event is waiting for its downstream consumer.
## Change
The trace job summary now adds:
| Field | `artifact_callback` | legacy `direct_mcp` |
|---|---|---|
| `execution_scope` | `arr_runtime` | `remote_agent` |
| `processor_mode` | `fixed_processor` | `legacy_direct_mcp` |
| `remote_dispatch` | `none` | `mcp` |
User-visible messages now say `ARR 固定处理器输出` and explicitly state `Finance 数据库提交已完成` before the
outbox wait. Existing `delivery_mode`, `remote_run_id`, trace version, persisted delivery IDs and privacy allowlist
remain compatible.
## Verification
- `python3 -m unittest tests.test_arr_web_job_trace tests.test_arr_web_task_log_ui tests.test_arr_web` — 27 passed.
- `python3 -m py_compile arr_web/job_trace.py` — passed.
- `node --check arr_web/static/app.js` — passed.
- `git diff --check` — passed.
- No live task was rerun, no service was restarted and no Finance/report/Booking business data was changed.
## Follow-up
After deployment, inspect one successful `artifact_callback` trace and confirm the additive header fields appear in the
deployed CLI/API/UI. This is a display/observability acceptance step; it is separate from rerunning the historical
2026-07 company-report `11-20` publication.

View File

@@ -0,0 +1,46 @@
# Evidence Topic: 2026-07 company-report retry publication failure
## Metadata
- Date: 2026-08-03
- Status: Implemented; live historical rerun pending
- Scope: 2026-07 company-channel report, C/O `11-20`, local Web job `60bcda9c8dae49758fb69dc47e0ff9e7`
- Confidence: Fact for recorded job/build observations; inference for the exact publisher guard
- Source: job JSON, prior successful job JSON, read-only controlled PostgreSQL snapshot, source-code inspection and temporary-directory double-build probe
- Last verified: 2026-08-03
- Stale trigger: changes to the company XLSX builder, artifact identity, reservation-version derivation or publisher idempotency
## Question
Why did the Web show `正式表格发布失败` for 2026-07?
## Evidence
- The failed job is for report month `2026-07`, period `11-20`, and as-of date `2026-07-20`. All five companies failed at stage `publish` with `COMPANY_REPORT_PUBLISH_FAILED`; there are no record IDs and no source/Booking/business validation errors.
- The immediately preceding job for the same month and period succeeded for all five companies and created archive versions. The failure is therefore a retry of an already published semantic snapshot, not a first-build data failure.
- `company_reports.repository.PostgresReportRepository.reserve_report()` derives the version directory from a deterministic semantic identity containing the company, period, as-of date, processor/rule/schema versions, daily-version pins and Booking-version pins. The retry therefore targets the same archive version path.
- A read-only double-build probe loaded the current controlled July snapshot and built each of the five valid `11-20` workbooks twice in separate temporary directories. For every company, the two binary SHA-256 values differed while the builder's `semantic_sha256` was identical.
- `AtomicReportPublisher._install_once()` rejects an existing archive path when its binary SHA-256 differs from the newly built file, raising `COMPANY_REPORT_PUBLISH_FAILED`. The result JSON also embeds the binary SHA-256, so allowing only the archive copy would still leave the same retry-identity conflict at result validation.
- No database write, report rerun or output repair was performed during this diagnosis.
## Implemented Fix
- `AtomicReportPublisher` now checks for an existing archive/result pair before creating legacy/current side effects. A
retry is reusable only when both files are regular files, the result JSON matches the deterministic report identity and
current semantic SHA, and the archive's actual SHA-256 matches the result JSON's stored artifact SHA.
- A matching retry returns the existing archive/result metadata and activates the repository without copying the new
XLSX bytes. An existing current file is left untouched; a missing current file is repaired from the existing archive.
- Partial, corrupt, hash-inconsistent or semantically conflicting publication state fails closed with
`PUBLISH_FAILED`. First publication and activation-failure rollback paths remain covered.
- Regression coverage passed for same-semantic/different-binary retry, semantic mismatch, partial publication and
corrupted archive. The real ArtifactTool integration regression is present but was skipped locally because the
optional `@oai/artifact-tool` dependency is unavailable.
- No live report rerun, output repair or business-data mutation was performed after the code change.
## Conclusion
The 2026-07 `11-20` failure was caused by a non-byte-deterministic XLSX build colliding with the publisher's deterministic archive/version identity on retry. The workbook contents were semantically the same, but changing XLSX bytes made the immutable archive and result identity appear conflicting. The existing successful 11-20 artifacts remain valid; the failed retry did not replace them. The publisher now handles this case idempotently by reusing that stable identity.
## Fix direction
The publisher-side idempotency fix is implemented and is the active remedy: reuse/validate the existing artifact by semantic identity and preserve one stable binary artifact identity. Making the XLSX builder byte-deterministic remains an optional future optimization, not a prerequisite for retry success. The historical job still needs a deliberate live rerun to verify the deployed path.

View File

@@ -0,0 +1,41 @@
# Booking Excel to company-report traceability
## Scope
The Booking Excel upload page could tell the operator that an identical workbook was already active, but the company-report
history and job detail did not show which source was associated with a generated result. This change adds source identity
without exposing workbook rows, guest data or full artifact hashes.
## Diagnosis
- `booking_ingestion/excel_review_postgres.py` deduplicates an identical activated artifact and raises
`BOOKING_EXCEL_SOURCE_ALREADY_ACTIVATED` with `这份 Excel 已经启用,无需重复提取`.
- `/api/company-reports/source` already returned a privacy-safe source summary, but the frontend used it only as a
readiness boolean.
- `POST /api/company-reports/jobs` accepted only month and period, and persisted no source metadata in the durable job
state. The frontend API helper also discarded error codes, preventing an informational duplicate-upload branch.
## Implemented behavior
- The server captures the current active source when a report job is submitted; the browser cannot choose or spoof the
source identity.
- New job records expose only source batch id/type, safe filename, activation time, disposition and summary counts.
- The company page shows the current active workbook and summary counts.
- Job detail and history show the source Excel captured at submission; old job files without this field render
`历史任务未记录来源`/an equivalent localized label.
- Re-uploading the same active workbook clears the selected file, refreshes the current-source and report-history views,
and shows an informational message. It does not create a second extraction or automatically generate a report.
- Report generation remains an explicit month/period action. Upload controls stay blocked while a report job is active, so
the submission-time source remains the trace shown for that job.
## Verification
- `./.venv/bin/python -m unittest tests.test_arr_web_company_reports tests.test_arr_web`: 28 tests passed.
- `./.venv/bin/python -m unittest discover -s tests -p 'test_arr_web*.py'`: 69 tests passed.
- `./.venv/bin/python -m unittest discover -s tests`: 360 tests passed, 10 optional skips.
- `node --check arr_web/static/app.js` and Python compilation passed.
- `git diff --check` passed.
The available in-app browser session landed on the login page without an authenticated session. No login, upload,
activation, report generation, service restart or business-data mutation was attempted; authenticated browser acceptance
remains the deployment follow-up.

View File

@@ -0,0 +1,30 @@
# Public Mobile H5 Dashboard
## Request
Make the mobile dashboard publicly viewable while preserving the authenticated Finance/operator surface.
## Public contract
- Anonymous `GET /h5`, `/h5.html`, `/assets/h5.css` and `/assets/h5.js` are allowed. `i18n.js` was already public as a
login asset.
- Anonymous `GET /api/public/h5/months` returns only month key, update time and maximum ARRIVAL watermark.
- Anonymous `GET /api/public/h5/analytics?month=YYYY-MM` returns aggregate channel/room metrics and date coverage.
The public projection omits `source_monthly_sha256`, filenames and operational metadata; JSON responses are
`Cache-Control: no-store`.
- H5 uses anonymous `/healthz` for its connection indicator and does not require a CSRF token for read-only data. When a
logged-in operator opens H5, the existing session is still detected and the optional logout control remains available.
## Protected contract retained
Anonymous desktop `/`, `/api/health`, generic `/api/months`, `/api/analytics`, legacy `/api/h5/months`, legacy
`/api/monthly/{month}/analytics`, jobs, traces, downloads, uploads, Booking/company/report routes and mutations remain
protected by the existing session/CSRF boundary.
## Verification
- `python3 -m unittest tests.test_arr_web_auth tests.test_arr_web` — 26 passed.
- `python3 -m unittest discover -s tests -p 'test_arr_web*.py'` — 71 passed.
- `node --check arr_web/static/h5.js`, `node --check arr_web/static/app.js` and `python3 -m py_compile arr_web/app.py` — passed.
- `git diff --check` — passed.
- No live service restart, public deployment, database write or business/report mutation was performed.