Two reviewer-identified defects in the yeeke return sync:
- acquire() wrote LeaseExpiresAt but nothing ever read it back, so a
crash/restart mid-run left a permanent active_slot=1 row blocking every
future sync. acquire() now runs a conditional takeover UPDATE first
(status=running AND lease_expires_at <= now -> failed, active_slot
cleared, error_message recorded), following the lease-with-expiry-
takeover idiom in order_writeback_worker.go. The takeover UPDATE is a
single statement so it is atomic per-row, and the
ux_yeeke_sync_run_active_slot unique index arbitrates a concurrent
takeover race the same way it already arbitrates two brand-new runs.
- itemKey() always appended the positional index, so a package whose items
come back in a different order on a later sync got new keys and
duplicate rows. The index fallback is now used only when i.ID, i.ItemID
and i.VariationID are all empty.
Added tests: TestStaleLeaseIsTakenOverOnNextAcquire,
TestValidLeaseIsNotTakenOver, TestConcurrentTakeoverExactlyOneWins,
TestItemKeyStableAcrossReorder,
TestItemKeyIndexFallbackForItemsLackingAllIDs,
TestItemKeyDistinctVariationsOfSameItemID.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Builds on cf70021, which added the read-only yeeke client/session/sync core
but left it unreachable and unconfigurable. This commit:
- Wires config.ExtConfig.Yeeke (settings.yml + config.yaml + GOAUTO_YEEKE_*
env vars), mirroring the existing SYB credential pattern exactly, with a
dedicated OcrURL and shared OCR client from sybclient.
- Adds yeeke.StartSync as the single entry point for both a manual admin
trigger and the scheduled job, sharing one in-memory gate plus the existing
DB-level unique active_slot lease so they can never run concurrently.
- Fixes sync.go bugs found in review: Service.Sync always returned a nil
error even when the run failed (start/resume semantics were untestable),
item upserts on ctx-less s.db calls, and no error_message/last_success_at
was ever recorded on the run row.
- Adds status_unrecognized to yeeke_return_package: an unknown claim status
is preserved verbatim and flagged rather than silently bucketed.
- Adds the admin read-only surface (GET .../sync-runs, GET
.../sync-runs/:runId, POST .../sync) under /api/admin/v1/yeeke-returns,
visible to admin and purchaser per the #336 review comment, registered as
a GoAuto access module/menu group and purchaser API.
- Registers GoAutoYeekeReturnSync in the existing job/lease framework
(app/jobs), seeded disabled (Status 2) by a new version-local migration,
following 1786701600000_syb_hourly_sync_job.go's pattern exactly.
- Expands tests: session reuse/bounded re-login/timeout-preserves-cache in
yeekeclient; paging robustness (total changing mid-run, duplicate page,
empty page, timeout, simulated restart/resume), idempotent upserts,
unrecognized-status flagging, active_slot lease contention, StartSync gate
contention, and a credential/captcha redaction check in yeeke; settings.yml
binding and env var precedence in config.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
- PddScreenParser: add REQUIRED_EVIDENCE fallback type after all existing
branches; requires address entry (masked phone in a nearby clickable row),
payment entry (specPanel.paymentAreaAliases in a nearby clickable row) and
one enabled quantity EditText, plus at least one auxiliary signal
(options, summary, close, +/- or submit action). +/- no longer required
on this path; existing branches unchanged.
- The three required items must share one panel container: their lowest
common ancestor must not be a window root and must not cover the whole
screen. Items from an address dialog, a payment dialog and an unrelated
input box are not recognized.
- Purchaser hints on SPEC_PANEL_EVIDENCE_NOT_MATCHED, the spec-panel wait
diagnostic and the address-save timeout when a required item is missing
(default WeChat Pay / default address / quantity input). No Alipay alias.
- Purchase spec-entry wait and post address-save wait: bounded 5000 ms,
fallback panels need two identical structure samples, no Back while
waiting, explicit failure on timeout; boolean/count diagnostics only.
- Final submit target additionally rejects zero-size labels/containers.
- Diagnostics: evidence flags (incl. same-container) in panel evidence and
spec-panel entry event.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
- PddScreenParser: nested clickable nodes of one labelled option block
(outer block, image, inner text with a badge such as "零差评") are
collapsed into one spec value named by the outer block (task 535). The
click node is still chosen by safeOptionRank; selected/checked flags are
merged from the block members; no badge-text stripping.
- PurchaseRehearsalExecutor: on SPEC_SELECTION_UNCONFIRMED emit a
specSelectionUnconfirmed diagnostic with the dimension, the target value
and the parsed option texts (each truncated to 40 chars) with selected
flags. Spec values are product attributes only.
- Tests: SpecOptionDedupTest with a sanitized task 535 fixture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Address review findings on 01510a8:
1. BLOCKER: sessionRetryBackoff summed to 30min, shorter than the up-to-
~60min gap between a session dying and the next hourly SYB sync
refreshing it. Changed to 5m/10m/15m/30m/30m (total 90min across
maxSessionRetryAttempts=6), updated the code comment to state the
~90min > one hourly sync period rationale, and added
TestSessionRetryBackoffTotalExceedsHourlySyncWindow to guard it.
2. Test gap: the CheckSession probe added inside
restoreOrderWritebackClient was only exercised through a fake
Factory, never through a real sybclient.Client. Added
httptest-backed tests that run restoreOrderWritebackClient against
an emulated /am/user/get (matching the envelope shape in
sybclient/client.go's `envelope` type): valid session returns a
client, mismatched username maps to ErrSessionInvalid, 5xx/timeout
map to a non-invalid error — each asserting the syb_session row is
left untouched. Added an end-to-end worker test using the real
Factory against the invalid-session server, asserting
failed/SYB_SESSION_UNAVAILABLE with a scheduled backoff and an
intact session row.
3. sessionUnavailableMessage: renamed the default category to
"会话恢复失败(网络/其他)" and wrapped every category in an
actionable template ("SYB会话不可用(<类别>),将自动重试;如持续
失败请恢复登录后重试"), still well under the 300-char column limit
and free of raw error text/credentials.
Tests: go vet ./app/goauto/purchase/... (clean); go test
./app/goauto/purchase/... (ok, 3.4s, includes the new httptest-backed
CheckSession coverage and the backoff-window guard).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
SYB order-number writeback silently gave up on session-class failures
(SYB_SESSION_UNAVAILABLE), requiring manual resubmit even though the
hourly sync job refreshes the session on its own. This adds a bounded,
backoff-scheduled auto-retry for that error code only:
- restoreOrderWritebackClient now actively probes the cached cookie
jar with sybclient.CheckSession after import, so a remotely-expired
session is classified as retryable up front instead of surfacing
later as SYB_READ_FAILED. It never logs in, never triggers OCR and
never deletes the cached session.
- The dropped Factory error is now categorized into a safe message
(no cookies/tokens) and recorded in error_message.
- The worker's claim query additionally picks up failed rows with
error_code=SYB_SESSION_UNAVAILABLE once their backoff
(lease_expires_at) has elapsed and attempt_count is below
maxSessionRetryAttempts=6 (1m/2m/4m/8m/15m growing backoff, chosen
to span the hourly sync window); other failure codes are unchanged.
- CanSubmit no longer hides manual resubmit during that backoff
window; manual resubmit resets attempt_count to 0 and clears the
lease so the worker cannot double-claim the same row.
Diff is limited to the purchase package; sybimport/sybclient/
sybinnercode are untouched.
Tests: go test ./app/goauto/purchase/... (new
order_writeback_session_retry_test.go covers backoff scheduling,
reclaim timing, max-attempt cutoff, CheckSession invalid/network
classification with no session deletion, CanSubmit during backoff,
manual resubmit reset, and non-session codes being excluded).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F