feat: add public h5 dashboard and publish traceability
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user