@itpay/cli 2.1.12 → 2.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/client/backend.js +4 -0
- package/dist/src/commands/checkout.js +109 -2
- package/dist/src/commands/guidance.js +33 -1
- package/dist/src/commands/order.js +8 -1
- package/dist/src/commands/presentation.js +153 -0
- package/dist/src/commands/rail_catalog.js +112 -0
- package/dist/src/commands/services.js +388 -36
- package/dist/src/main.js +26 -1
- package/dist/src/render/browser.js +65 -0
- package/dist/src/state/account_auth.js +196 -49
- package/dist/src/state/client_context.js +70 -0
- package/dist/src/state/config.js +13 -2
- package/dist/src/state/task_journal.js +88 -0
- package/docs/agent/buyer/identity-and-sessions.json +9 -1
- package/docs/agent/buyer/rail-booking.json +16 -2
- package/docs/agent/buyer/rail-fast-checkout.json +67 -0
- package/docs/cli-reference/commands/checkout.md +10 -1
- package/docs/reviews/agent-ux-handoff-2026-09-23/IMPLEMENTATION-EVIDENCE.md +174 -0
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +55 -3
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"A query returned login_required or rate_limited."
|
|
14
14
|
],
|
|
15
15
|
"service_selection": {
|
|
16
|
-
"itpay-rail-exact": "Use when the buyer knows BOTH the exact departure and arrival railway station names (e.g. 古镇 → 广州南).
|
|
16
|
+
"itpay-rail-exact": "Use when the buyer knows BOTH the exact departure and arrival railway station names (e.g. 古镇 → 广州南). It is one station-pair + one travel_date query: filter the returned list locally for remembered train numbers or time windows — do NOT re-query per train. Do NOT use it with only a city or fuzzy location.",
|
|
17
17
|
"itpay-rail-smart": "Use when the buyer has a city name, an area, an address, or wants transfer planning and recommendations. The platform resolves locations to stations itself; never silently convert a city into its namesake station.",
|
|
18
18
|
"itpay-rail-booking": "Purchase service — use ONLY after the buyer explicitly chose a train and seat from a query result. Starts quote → protected checkout → payment → asynchronous issuance on one execution. Never queried for schedules.",
|
|
19
19
|
"svc_qizhidao_company_lookup": "Not a railway service; do not mix intents."
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"explicit_legs": {
|
|
69
69
|
"passengers": "Integer 1-5.",
|
|
70
|
-
"legs": "Array
|
|
70
|
+
"legs": "Array with exactly ONE leg: from_station_code, to_station_code, travel_date, train_code, departure_time (HH:MM), arrival_time (HH:MM), arrival_days (int 0-7), seat_type. The public review accepts a single leg; only use when no booking_offer is available, and never guess these values. For v2 separate_legs_only journeys, book one leg at a time via that leg's own booking_offer — never a multi-leg basket."
|
|
71
71
|
},
|
|
72
72
|
"boundary": "Identity fields (names, ID numbers, phones) never belong in this input; they are collected on the protected checkout page."
|
|
73
73
|
}
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"Submit input with services run <service> --execution <id> --input-json <file> --json on the SAME execution.",
|
|
93
93
|
"itpay-rail-smart may stop at a location confirmation step; show the candidate places to the buyer, then continue the same execution after the human confirms.",
|
|
94
94
|
"After the buyer explicitly picks a train/seat, run itpay-rail-booking with the selection token (preferred) or explicit legs. The execution then pauses at the booking review (workflow:confirm_booking): services next returns booking_review_required with human_action.context.review carrying draft_revision, legs, seat_options and notice_version.",
|
|
95
|
+
"Whether or not the query was free, the answer is the same shape under compare: one preferred option, at most two meaningful alternatives, the key trade-offs — then stop and wait for the buyer. Do not stack more options or chain into purchase.",
|
|
95
96
|
"Collect the review input and submit it with services action <id> --action workflow:confirm_booking --actor-type human --status approved --input-json <file> --json. Required fields: draft_revision (exact server value), passengers, seat_type, seat_preferences (one entry per passenger_index 0..n-1, values limited to that leg's seat_options), party_preference, fallback=automatic_assignment, notice_version, accept_non_guaranteed=true. Only after an accepted review does the server quote and offer checkout.",
|
|
96
97
|
"The booking execution reaches a protected checkout: passenger identity data is entered by the human on that page, never through the CLI. Account authentication may happen inside checkout — do not force a separate auth login first (Path B).",
|
|
97
98
|
"Payment success does not mean the ticket is issued. Issuance is asynchronous; keep reading the same execution with services next.",
|
|
@@ -122,6 +123,7 @@
|
|
|
122
123
|
},
|
|
123
124
|
"agent_rules": [
|
|
124
125
|
"Pick the service by the buyer's input precision: exact station names → itpay-rail-exact; anything fuzzier → itpay-rail-smart; explicit purchase intent after a selection → itpay-rail-booking.",
|
|
126
|
+
"Choose the goal first (see goals): compare presents one preferred option plus at most two meaningful alternatives then waits; prepare_checkout selects under the buyer's rules and continues to the official confirmation entry without re-asking.",
|
|
125
127
|
"Never invent input field names. If the guidance block or this doc does not list a field, do not send it.",
|
|
126
128
|
"Never ask the buyer for passenger names, ID numbers, or phone numbers in chat; those are collected only on the protected checkout page.",
|
|
127
129
|
"Never reconstruct provider-shaped legs JSON when a booking_offer.selection_token is available; submit the token plus passengers.",
|
|
@@ -187,5 +189,17 @@
|
|
|
187
189
|
"journey_card": "Cards carry station names, train codes, dated times, transfer/same-run notes, observed_at price time and handles. representative_offer.ticket_offers[] lists one purchasable segment per entry (leg_index, offer_id, service_ref, station codes, travel_date, seat_type, passengers, integer-minor amounts); per-item amounts sum to the total. rides[] (ridden legs) and ticket_offers[] (purchasable segments) are different lists — never infer transfers or fees from array lengths.",
|
|
188
190
|
"selection": "booking_support=single_leg cards carry booking_offer {service_id: itpay-rail-booking, selection_token: rsel_…}; submit it to itpay-rail-booking with passengers as today. separate_legs_only cards expose no whole-journey token — each leg is quoted and confirmed separately via its protected detail; never assemble legs JSON yourself. booking_support=none has no selection action.",
|
|
189
191
|
"honesty": "Coverage is bounded: repeat searched_scope/notices verbatim, never claim national-exhaustive search, and surface missing prices as unknown — never 0."
|
|
192
|
+
},
|
|
193
|
+
"goals": {
|
|
194
|
+
"compare": "Buyer wants to see options and keeps the final choice. Present one preferred option plus at most two meaningful alternatives, then wait. A fully-specified query with no purchase delegation stays compare.",
|
|
195
|
+
"prepare_checkout": "Buyer named the product, or delegated selection under explicit rules and asked for the purchase entry. Select under those rules, explain the match, and continue to the official confirmation/payment entry without re-asking '要不要买'. Clear delegation missing only the date asks for the date alone.",
|
|
196
|
+
"boundaries": [
|
|
197
|
+
"Decide locally from the whole conversation (negations, conditions, references, latest withdrawal); no trigger-word lists, no regex, no field-completeness classifier, no mode flag, never ask the human to name a mode.",
|
|
198
|
+
"Explicit buyer rules outrank default recommendations; missing facts are unknown, never assumed satisfied.",
|
|
199
|
+
"compare must not continue into selection; prepare_checkout must not silently downgrade to waiting.",
|
|
200
|
+
"A pending/login/error state is never new intent; withdrawal updates the goal but never rewrites a completed payment.",
|
|
201
|
+
"Neither goal authorizes payment or skips confirmation: payment, terms and passenger identity stay behind their own gates."
|
|
202
|
+
],
|
|
203
|
+
"recipe": "For the query→booking→review→checkout continuity after a valid selection, load the rail-fast-checkout topic (itpay docs show rail-fast-checkout --json)."
|
|
190
204
|
}
|
|
191
205
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "itp.agent_doc.v1",
|
|
3
|
+
"role": "buyer_agent",
|
|
4
|
+
"product_scope": "itpay_rail",
|
|
5
|
+
"topic": "rail-fast-checkout",
|
|
6
|
+
"title": "Rail Fast Checkout — One Human Question, Owners Do The Rest",
|
|
7
|
+
"purpose": "After the buyer has picked a train/seat (prepare_checkout delegation), carry one valid selection through booking review, protected checkout and issuance tracking with a single consolidated human interaction — using only existing commands and owner-issued handles.",
|
|
8
|
+
"when_to_use": [
|
|
9
|
+
"The buyer explicitly picked a train/seat or delegated selection under rules, and you hold a bookable option with booking_offer.selection_token from itpay-rail-exact or itpay-rail-smart.",
|
|
10
|
+
"Do NOT use for a plain schedule query (compare goal) — present one preferred option plus up to two alternatives and wait instead."
|
|
11
|
+
],
|
|
12
|
+
"steps": [
|
|
13
|
+
{
|
|
14
|
+
"step": "create_booking_input",
|
|
15
|
+
"command": "printf '%s' '{\"passengers\":1,\"selection\":{\"token\":\"<token>\"}}' > /tmp/itpay-booking-input.json",
|
|
16
|
+
"notes": [
|
|
17
|
+
"Fill <token> with the current result's booking_offer.selection_token; add selection.seat_type when the buyer picked a class. Create the file yourself under /tmp — the buyer never sees a path.",
|
|
18
|
+
"Never reconstruct legs JSON when a token is available; the owner derives authoritative legs and re-quotes live."
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"step": "start_booking",
|
|
23
|
+
"command": "itpay services run itpay-rail-booking --input-json /tmp/itpay-booking-input.json --json",
|
|
24
|
+
"then": "itpay services next <execution_id> --json",
|
|
25
|
+
"expected_status": "booking_review_required"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"step": "one_consolidated_review",
|
|
29
|
+
"read": "human_action.context.review carries legs, seat_options, draft_revision and notice_version.",
|
|
30
|
+
"ask_human_once": "Show the itinerary, seat options and the seat-preference disclaimer in one message; collect passengers count, seat_type, one seat preference per passenger slot, party_preference, and agreement to the notice_version terms.",
|
|
31
|
+
"web_review": "If the review projection carries an official web entry (handoff.url in the envelope), prefer it: open it for the buyer and wait there; the chat fallback remains valid if the page is unavailable.",
|
|
32
|
+
"submit": "itpay services action <execution_id> --action workflow:confirm_booking --actor-type human --status approved --input-json /tmp/itpay-review.json --json",
|
|
33
|
+
"notes": [
|
|
34
|
+
"draft_revision must equal the current server value; booking_review_changed means re-read services next and resubmit, never bump locally.",
|
|
35
|
+
"accept_non_guaranteed is true only when the buyer actually agreed; never fabricate a human approval.",
|
|
36
|
+
"Passenger names, ID numbers and phones are NOT in this input — they belong to the protected checkout page."
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"step": "checkout_handoff",
|
|
41
|
+
"read": "services next returns checkout_required/payment_pending with handoff.url (the protected checkout page).",
|
|
42
|
+
"command": "itpay checkout --id <checkout_id> --token <display_token> --present auto --json",
|
|
43
|
+
"notes": [
|
|
44
|
+
"Show or open the checkout exactly once; pass --viewer mobile when the buyer is on a phone.",
|
|
45
|
+
"The buyer completes identity and payment on that page (Path B — no forced separate login). Payment handoff is not payment success; wallet return is not confirmation."
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"step": "track_issuance",
|
|
50
|
+
"command": "itpay services next <execution_id> --json",
|
|
51
|
+
"then": "itpay orders show <order_id> --json",
|
|
52
|
+
"expected_status": ["paid_pending_issuance", "issued"],
|
|
53
|
+
"notes": [
|
|
54
|
+
"Issuance is asynchronous — paid does not mean ticketed. Keep reading the SAME execution; never create a replacement.",
|
|
55
|
+
"On issued, report the order state; if seat details are still syncing, advise checking 12306 instead of re-querying."
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"never": [
|
|
60
|
+
"Never build legs JSON from visible result fields while a selection_token exists.",
|
|
61
|
+
"Never run a second services run for the same selection — repeat submission restores the same execution.",
|
|
62
|
+
"Never ask for passenger identity, verification codes, or payment details in chat.",
|
|
63
|
+
"Never claim payment equals issuance, or a displayed handoff equals human approval.",
|
|
64
|
+
"Never invent command flags, action types, revisions or tokens; copy server-issued handles verbatim."
|
|
65
|
+
],
|
|
66
|
+
"search_terms": ["fast checkout", "快速下单", "一键购票", "rail checkout recipe"]
|
|
67
|
+
}
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
itpay checkout [--id <checkout_id>] [--token <display_token>]
|
|
16
|
-
[--host <host>] [--target <target>] [--locale <zh-CN|en>]
|
|
16
|
+
[--host <host>] [--target <target>] [--locale <zh-CN|en>]
|
|
17
|
+
[--present <auto|browser|image|link|none>] [--no-open] [--viewer <desktop|mobile|unknown>]
|
|
18
|
+
[--relay-option <ref>] [--confirm-relay] [--request-key <key>] [--relay-status <relay_id>]
|
|
19
|
+
[--json]
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
省略 `--id/--token` 时只能使用本机保存的一组完整句柄;不得把其他 Checkout 的 token 拼接使用。
|
|
@@ -22,6 +25,12 @@ itpay checkout [--id <checkout_id>] [--token <display_token>]
|
|
|
22
25
|
|
|
23
26
|
`--locale` 默认 `zh-CN`,可显式使用 `en`。它只改变重新渲染的 Card 文案,不改变 Checkout、付款、授权或恢复状态。
|
|
24
27
|
|
|
28
|
+
展示控制(display plane,不创建任何新的付款路径):
|
|
29
|
+
|
|
30
|
+
- `--present` 一次只选一种展示方式:`browser` 打开系统浏览器、`image` 展示官方二维码图片、`link` 给出可点击链接、`none` 只说明状态、`auto`(默认)按宿主能力选择一种。`--json` 下默认只返回展示计划而不执行打开;显式 `--present browser` 即是一次打开请求。`ITPAY_NO_BROWSER=1` 与 `--no-open` 都禁止打开浏览器。已付款、已退款或报价失效的 Checkout 不会发起任何展示动作,只读取原状态。
|
|
31
|
+
- `--viewer` 告知用户正在看哪类设备:`mobile` 时不会把二维码当作同机可读方式;`desktop` 时才考虑打开浏览器。
|
|
32
|
+
- 消息转发(relay)要求服务端已签发的脱敏联系人引用 `--relay-option`、稳定幂等键 `--request-key` 和显式人类同意 `--confirm-relay`,三者缺一不可;CLI 不自行发送短信或邮件。`--relay-status <relay_id>` 只读取一次既有转发结果,绝不重发。`--present` 与 relay 互斥。当前 Backend 未提供 relay 能力时会如实返回不可用。
|
|
33
|
+
|
|
25
34
|
## 等待付款输出
|
|
26
35
|
|
|
27
36
|
```json
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Agent UX Handoff — CLI Implementation Evidence
|
|
2
|
+
|
|
3
|
+
Implements the W0–W7 contract from
|
|
4
|
+
`compose:docs/reviews/agent-ux-handoff-2026-09-23/README.md` (PR #215,
|
|
5
|
+
branch `spec/agent-ux-human-handoff-20260923`). Scope: `@itpay/cli` only, plus
|
|
6
|
+
the one compose-side AUTH05 gate fix. No Planner V2 behavior changed; no new
|
|
7
|
+
payment path; protected Checkout remains the only identity/payment surface.
|
|
8
|
+
|
|
9
|
+
## W0 — Baseline fingerprint
|
|
10
|
+
|
|
11
|
+
| Item | Value |
|
|
12
|
+
|---|---|
|
|
13
|
+
| CLI baseline commit | `760d20420ab71b7df0e61540bf5fd93543bfeb5a` |
|
|
14
|
+
| Package version | `@itpay/cli@2.1.17` (already published to `next`) |
|
|
15
|
+
| Node / npm | v24.10.0 / 11.6.1 |
|
|
16
|
+
| Baseline tests | 292/292 pass at baseline |
|
|
17
|
+
| Post-change tests | 309/309 pass (`npm test`), `npm run test:package` → `packed CLI smoke passed` |
|
|
18
|
+
| Coverage gate | lines 85.79 / statements 85.79 / functions 93.24 / branches 77.06 — all above thresholds (85/85/90/70) |
|
|
19
|
+
| Compose side | AUTH05 gate change + unit test; `go build ./...` clean, `internal/app/identity` + `internal/adapters/postgres` tests pass |
|
|
20
|
+
|
|
21
|
+
## Contract preserved / extended
|
|
22
|
+
|
|
23
|
+
- `status`/`result`/`handoff`/`instruction`/`next`/`recovery` unchanged.
|
|
24
|
+
- New optional `interaction` (`itpay.interaction.v1`: `stage`, `by_goal`,
|
|
25
|
+
`input_template`, `recipe`) and `communication` (`itpay.communication.v1`:
|
|
26
|
+
`status_line`, `recommended_reason`, `human_steps`, `next_expectation`,
|
|
27
|
+
`must_convey`).
|
|
28
|
+
- All nested `itpay ` command strings inside `result`, `handoff`,
|
|
29
|
+
`interaction` and `communication` are qualified by `qualifyCommandsDeep`
|
|
30
|
+
(Agent Type + Backend URL) at envelope write time (`src/commands/guidance.ts`).
|
|
31
|
+
- `next` is `null` whenever no fully-bound executable exists; unresolved
|
|
32
|
+
values live in `input_template` with `required_input` + `executable:false`.
|
|
33
|
+
|
|
34
|
+
## Issue → fix → verification map
|
|
35
|
+
|
|
36
|
+
### AUTH (src/state/account_auth.ts, src/state/task_journal.ts, src/main.ts, tests/ux_envelope.test.ts)
|
|
37
|
+
|
|
38
|
+
| Issue | Fix | Test |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| AUTH01 pending compression | `stage` is reported verbatim; `email_verification_required`/`merge_confirmation_required` get distinct instructions | `AUTH01: distinct pending stages…` |
|
|
41
|
+
| AUTH02 completed→replaced | completed open session binds in place via stored start_token; no second auth session is created | `AUTH02: binds in place` (asserts zero POST to `/v1/dashboard/auth-sessions`) |
|
|
42
|
+
| AUTH03 transport→new session | poll/bind/绑定状态 transport failure → `auth_status_unknown`; saved session file preserved; no new request | `AUTH03: transport failure keeps the saved session…` |
|
|
43
|
+
| AUTH04 placeholder resume | `TaskJournal` (`~/.itpay-v3/tasks.<env>.json`) records paused executions; `authenticated` resumes the exact journaled command or explicitly says none exists | `AUTH04: resumes the exact journaled task` + `…says so instead of emitting a placeholder` |
|
|
44
|
+
| AUTH05 wallet login gate | compose: `RequireVerifiedContact` = phone OR email verified; Checkout-bound sessions keep Path B exemption | `TestDashboardWalletGateAcceptsPhoneOrEmailAndKeepsCheckoutExemption` |
|
|
45
|
+
| AUTH06 raw JSON output | `itpay auth login|status` goes through `writeCommandEnvelope` (qualified, human-readable non-JSON, clean `--json`) | exercised via `agentAuth` envelope assertions |
|
|
46
|
+
| AUTH07 status classification | `auth_session_missing` (404/410), `auth_status_unknown` (transport), `login_required` (no session), terminal states distinct (`auth_expired/denied/cancelled/failed`) | `AUTH07: server-side missing…` + `terminal auth states…` |
|
|
47
|
+
| SEC01 credential exposure | auth handoff marked `contains_credentials: true`; instruction forbids writing start_token into chat/logs | code review + handoff field assertion |
|
|
48
|
+
|
|
49
|
+
### W3 services continuity (src/commands/services.ts)
|
|
50
|
+
|
|
51
|
+
| Requirement | Implementation |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Two-goal semantics | `interaction.by_goal.compare` (present ≤3 options, stop) / `prepare_checkout` (select under user rules → continue to official entry, no re-ask) on `query_results_ready` |
|
|
54
|
+
| No fake executable next | `select`/`book`/`<rank>`/`<file>` placeholders moved to `interaction.input_template`/`booking_template` with `executable:false`; `next` holds only fully-bound commands or `null` |
|
|
55
|
+
| Server-issued actions verbatim | `available_actions[]` copied through untouched |
|
|
56
|
+
| `planning` progress state | `communication` block (status_line/next_expectation/must_convey) without selection semantics |
|
|
57
|
+
| Booking review | `booking_review_required` keeps legs/seat_options/draft_revision/notice_version disclosure; consumes optional `review.web_review_url|entry_url` as `handoff` when the owner projects one (W4 web-review seam); chat consolidated review remains fallback; never fabricates approval |
|
|
58
|
+
| Journal writes | `runServicesNext` observes every envelope into `TaskJournal` |
|
|
59
|
+
|
|
60
|
+
### W5 presentation/relay (src/state/client_context.ts, src/commands/presentation.ts, src/render/browser.ts, src/commands/checkout.ts, src/main.ts)
|
|
61
|
+
|
|
62
|
+
| Requirement | Implementation |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Context extension | `ViewerDevice`, `ExecutorLocality`, `ClientCapabilities`, `hostCapabilities()` — Agent Type is never used as viewer/executor proxy |
|
|
65
|
+
| Deterministic resolver | `resolvePresentation`: business blockers first (paid/refund/unknown-amount → `read_same_order`, expired quote → `recover_expired_quote`, policy → blockers); then 1 recommended + ≤2 alternatives; explicit choice honored only when feasible; failed routes never silently retried; unknown capability ≠ yes; QR never offered to the mobile device being looked at; remote executor can't open a local browser |
|
|
66
|
+
| `itpay checkout` flags | `--present auto\|browser\|image\|link\|none`, `--no-open`, `--viewer`, `--relay-option`, `--confirm-relay`, `--request-key`, `--relay-status`; present⊥relay mutually exclusive; `--json` plans without side effects, explicit `--present browser` is the display request |
|
|
67
|
+
| Browser opener | argv-array spawn (no shell), HTTPS official origins only, loopback only behind `ITPAY_CLI_DEV=1`, no userinfo/arbitrary schemes; `dispatch_accepted` ≠ `page_loaded` |
|
|
68
|
+
| Relay | `resolveRelay` enforces issued-option + request-key + explicit consent; backend capability absent → honest `unavailable`; `--relay-status` read-only |
|
|
69
|
+
|
|
70
|
+
## Evidence separation (per W0/W7)
|
|
71
|
+
|
|
72
|
+
| Class | Count | Where |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| In-process unit/contract tests (new) | 17 | `tests/ux_envelope.test.ts` |
|
|
75
|
+
| Existing suite updated to new contract | 9 assertions moved to `interaction.*` | `tests/smoke.test.ts` |
|
|
76
|
+
| Mock backend extended | auth sessions + binding endpoints + failure injection | `tests/mock_backend.ts` |
|
|
77
|
+
| Packed-package smoke | pass | `npm run test:package` |
|
|
78
|
+
|
|
79
|
+
## NOT executed — remains open for acceptance
|
|
80
|
+
|
|
81
|
+
- No target-agent (Astra/harness) runs against real models.
|
|
82
|
+
- No real browser/native-host display verification — only `isPresentableURL` +
|
|
83
|
+
argv construction are unit-tested; `openInSystemBrowser` spawn is not e2e.
|
|
84
|
+
- No SMS/email/wallet relay test — backend relay endpoints do not exist; CLI
|
|
85
|
+
returns honest `unavailable`.
|
|
86
|
+
- No payment/issuance/refund flow test — those owners were untouched.
|
|
87
|
+
- Tarball-installed CLI behavior verified only by `scripts/package-smoke.mjs`,
|
|
88
|
+
not by a manual install run.
|
|
89
|
+
- The 72-check matrix from `VALIDATION.json` maps to implementation status
|
|
90
|
+
here; actual model/host acceptance rows stay "not executed" — this document
|
|
91
|
+
does not claim them.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Addendum — 09 Checkout Lifecycle & UX Repair (compose `codex/checkout-lifecycle-ux`, base `59f95a1`)
|
|
96
|
+
|
|
97
|
+
Spec: `09-CHECKOUT-LIFECYCLE-AND-UX-REPAIR-20260927.md`. Implemented the
|
|
98
|
+
server-owned payment deadline, checkout-scoped phone reuse, in-page desktop QR,
|
|
99
|
+
and shared deadline projection; CLI consumes the same fields.
|
|
100
|
+
|
|
101
|
+
### Single payable deadline D (backend)
|
|
102
|
+
|
|
103
|
+
| Requirement | Implementation | Test |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| One deadline everywhere | `checkouts.expires_at` = `min(created_at + PaymentTTL(15m), earliest bound quote-lock expiry)` set in `CreateCheckout`; copied verbatim to `orders.payment_deadline_at` by `EnsurePendingOrderForAuthorizedCheckout`; legacy rows fall back to `PaymentDeadline(created_at, ttl, quote expiry)`, never re-anchored | `TestLifecycleSingleDeadlineRealPG`, `TestLifecycleEarlyQuoteWinsDeadlineRealPG` (real PG) |
|
|
106
|
+
| Refresh never extends | persisted `expires_at` wins; reused intents keep their expiry | same test: second intent call returns identical `expires_at`, zero extra provider calls |
|
|
107
|
+
| Expiry truly cancels | `ExpireStalePendingOrders` single-tx cascade: order→cancelled, checkout→expired, intents→expired, quote locks→expired, bindings→expired, executions→cancelled+event, display tokens→expired, auth sessions→expired; `FOR UPDATE SKIP LOCKED`; orphan executions swept only when no live payment path remains | `TestLifecycleExpirySweepCascadeRealPG`: pre-D no-op, post-D full cascade, repeat sweep idempotent |
|
|
108
|
+
| Paid protected | candidate scan requires `status='pending_payment'`; conditional UPDATE loses the race to a verified payment | `TestLifecyclePaidSurvivesAndLateCallbackReconcilesRealPG` |
|
|
109
|
+
| Late provider funds | `CompleteProviderPayment` returns `Reconciled:true`, persists funds evidence, never resurrects cancelled order | same test: late TRADE_SUCCESS after sweep → reconciled, order stays `cancelled` |
|
|
110
|
+
| New actions refused at D | `ErrPaymentWindowClosed` → HTTP 410 `payment_window_closed`; `<1min` remaining refuses new QR/wallet actions; `paymentActionExpiry = min(now+actionTTL, D)` clamped into provider `TimeoutExpress`/`time_expire` | unit tests in `internal/app/payment`, `internal/httpapi/handlers`; PG test asserts intent `expires_at = D` |
|
|
111
|
+
|
|
112
|
+
### Phone (checkout-scoped, privacy-preserving)
|
|
113
|
+
|
|
114
|
+
- `ports.BuyerProfile` exposes `LoginPhone`/`PhoneVerified`; presentation projects
|
|
115
|
+
`account_phone_option` = `{source, masked_recipient}` only — the raw number
|
|
116
|
+
never leaves the server.
|
|
117
|
+
- `order_contacts` rows are checkout-scoped: `InsertAccountOrderContact` copies
|
|
118
|
+
the verified account phone under a row lock (refused on expired/terminal
|
|
119
|
+
checkout, `ErrOrderContactLocked` once a payment action exists);
|
|
120
|
+
`GetVerifiedOrderContact` feeds `RequireDeliveryContact` and the masked
|
|
121
|
+
`verified_sms_contact` projection.
|
|
122
|
+
- Replacement phone = OTP-verified `order_contact` challenge; it never writes
|
|
123
|
+
the account login phone. `UpdateDeliveryContact` gates `phone_source:
|
|
124
|
+
account_phone` on a live buyer session and reads the account-side verified
|
|
125
|
+
number server-side — client-submitted digits are not trusted.
|
|
126
|
+
- Real-PG tests: `TestLifecycleOrderContactScopeRealPG` covers reuse
|
|
127
|
+
roundtrip, cross-checkout isolation, upsert versioning, post-payment lock,
|
|
128
|
+
expired-checkout refusal.
|
|
129
|
+
|
|
130
|
+
### Shared projection & clients
|
|
131
|
+
|
|
132
|
+
- `CheckoutPresentation`/`Order`/buyer summaries emit `payment_deadline_at`,
|
|
133
|
+
`server_now`, `payment_remaining_seconds`; expired pending orders project
|
|
134
|
+
`cancelled` and drop the deadline field.
|
|
135
|
+
- Web: `paymentCountdown.ts` ticks from server values only (never extends);
|
|
136
|
+
`PaymentDeadlineBadge` shared by OrdersPage/OrderDetailPage; CheckoutPage
|
|
137
|
+
disables payment at zero and re-reads the backend; desktop Alipay QR renders
|
|
138
|
+
in-page via `qrcode` with the copy "展示付款码不会直接扣款".
|
|
139
|
+
- CLI: `itpay checkout`/`itpay order` envelopes carry the same deadline fields;
|
|
140
|
+
pending instructions state the exact deadline and that refresh does not
|
|
141
|
+
extend it; expiry guidance routes to re-quote rather than replaying stale
|
|
142
|
+
payment actions. `Order`/`CheckoutPresentation` types extended in
|
|
143
|
+
`src/client/types.ts`.
|
|
144
|
+
|
|
145
|
+
### Harness fix (per §6)
|
|
146
|
+
|
|
147
|
+
- `ux_harness.py` interventions now classify `human_product_step` (login/OTP/
|
|
148
|
+
passenger confirmation/QR scan — requires step+operator+scope+a same-order
|
|
149
|
+
backend_observation in `verification_event_ids`) vs `engineering_rescue`
|
|
150
|
+
(still fail); unclassified legacy entries stay conservative.
|
|
151
|
+
- `relative_file` no longer rejects runs under symlinked tempdirs (macOS
|
|
152
|
+
`/var`→`/private/var`); symlink checks now apply inside the run dir only.
|
|
153
|
+
- Prompts updated (UI-OPERATOR/USER-SIMULATOR/ASTRA-REVIEWER/README).
|
|
154
|
+
- `test_harness.py` 25/25, including 6 new classification cases.
|
|
155
|
+
|
|
156
|
+
### Actually executed (this round)
|
|
157
|
+
|
|
158
|
+
- Backend unit: `go test ./internal/app/{checkout,payment,order,railbooking,identity} ./internal/presenter ./internal/httpapi/handlers` — all green.
|
|
159
|
+
- Real PG (`ITPAY_V3_TEST_DATABASE_URL`, isolated per-test DB): 6 new
|
|
160
|
+
lifecycle tests + golden regression suite (`SearchSelectCheckoutGoldenFlow`,
|
|
161
|
+
receipt aggregation, provider-failure, quota transition) — all green.
|
|
162
|
+
- Web: `npm run typecheck` clean; `npm test` 73/73.
|
|
163
|
+
- CLI: 309/309 tests (240 smoke + 69 unit), tarball package smoke previously
|
|
164
|
+
green; contract/package `tsc` clean.
|
|
165
|
+
- Harness: `python3 -m unittest test_harness` 25/25.
|
|
166
|
+
|
|
167
|
+
### NOT executed (unchanged honesty)
|
|
168
|
+
|
|
169
|
+
- No real Alipay/WeChat channel call — fake `AlipayPaymentClient` records
|
|
170
|
+
requests only; provider sandbox validation remains open.
|
|
171
|
+
- No real browser e2e of the QR modal / countdown; no real-device OTP.
|
|
172
|
+
- No deployed dev-environment verification of the new checkout path.
|
|
173
|
+
- Harness changes are structural (classifier + fixtures); no real triadic run
|
|
174
|
+
was executed.
|
package/package.json
CHANGED
package/skills/itpay/SKILL.md
CHANGED
|
@@ -41,13 +41,53 @@ Three services, three different jobs — pick by the human's input, never merge
|
|
|
41
41
|
| City/area/address, or wants transfer planning | `itpay-rail-smart` (location resolution + planning) |
|
|
42
42
|
| Chose a train/seat and wants to buy | `itpay-rail-booking` (quote → protected checkout → issuance) |
|
|
43
43
|
|
|
44
|
+
### Choose The Goal First
|
|
45
|
+
|
|
46
|
+
From the full context — negations, conditions, references, the latest
|
|
47
|
+
withdrawal — decide locally between two goals. Never use keyword lists,
|
|
48
|
+
regular expressions, or field-completeness as a classifier, and never ask the
|
|
49
|
+
human to name a mode. There is no `--mode` flag.
|
|
50
|
+
|
|
51
|
+
- `compare` — the human wants to see options and keeps the final choice.
|
|
52
|
+
Present one preferred option plus at most two meaningful alternatives, then
|
|
53
|
+
stop and wait. All fields being present does not authorize a purchase.
|
|
54
|
+
- `prepare_checkout` — the human named the product, or delegated selection
|
|
55
|
+
under explicit rules and asked for the purchase entry. Select a valid option
|
|
56
|
+
under those rules, then continue to the official confirmation/payment entry
|
|
57
|
+
without asking "要不要买" again. If the delegation is clear but one required
|
|
58
|
+
fact (e.g. the date) is missing, ask only for that fact — not whether to buy.
|
|
59
|
+
|
|
60
|
+
Default to `compare` when delegation is unclear. A pending, login, or error
|
|
61
|
+
state is never new user intent; withdrawal updates the goal immediately but
|
|
62
|
+
never rewrites a completed payment.
|
|
63
|
+
|
|
64
|
+
Explicit user rules outrank default recommendations: "13:00之后最早够两张二等座,
|
|
65
|
+
没有就往后找" means same date, same station pair, seat-class + quantity filter,
|
|
66
|
+
departure-time order — not per-train re-queries, date changes, station swaps,
|
|
67
|
+
upgrades, waitlists, or endless retries. Missing facts are `unknown`, never
|
|
68
|
+
assumed satisfied. "08:00出门" includes ground transfer and station buffer;
|
|
69
|
+
"当天到" checks the calendar date, not just the clock time.
|
|
70
|
+
|
|
71
|
+
`itpay-rail-exact` is one station-pair + date query — filter the returned list
|
|
72
|
+
locally rather than re-querying per remembered train number. Model memory
|
|
73
|
+
supports route hypotheses only, never same-day schedule/price/inventory facts.
|
|
74
|
+
A saved snapshot's pages, journey detail and rerank are free reads; a new live
|
|
75
|
+
observation needs the owner's authorization.
|
|
76
|
+
|
|
77
|
+
After a valid selection under `prepare_checkout`, the short
|
|
78
|
+
`rail-fast-checkout` recipe covers query→booking→review→checkout continuity:
|
|
79
|
+
`itpay docs show rail-fast-checkout --json`. It reuses existing
|
|
80
|
+
`services run`/`action`/`checkout` — no fast-buy command, no payment bypass,
|
|
81
|
+
no fabricated human approval.
|
|
82
|
+
|
|
44
83
|
Query results carry a server-issued `booking_offer.selection_token` on bookable
|
|
45
84
|
options; for a purchase submit that token plus passenger count — never
|
|
46
85
|
reconstruct train legs by hand. Passenger names, ID numbers and phone numbers
|
|
47
86
|
belong only on the protected checkout page, never in chat. Each query service
|
|
48
|
-
has its own free trial count
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
has its own free trial count
|
|
88
|
+
(2+2, not a shared pool); `login_required` pauses the query, it is not a
|
|
89
|
+
failure — resume the same execution after `itpay auth login`. For the full
|
|
90
|
+
input contract and states, load the rail-booking topic via
|
|
51
91
|
`itpay docs search rail-booking --json`.
|
|
52
92
|
|
|
53
93
|
## Follow One Envelope
|
|
@@ -61,6 +101,18 @@ For each JSON response:
|
|
|
61
101
|
and any required human action is complete.
|
|
62
102
|
5. Use `recovery` only when the normal continuation cannot proceed.
|
|
63
103
|
|
|
104
|
+
Newer envelopes may also carry an `interaction` block (`stage`, `by_goal`,
|
|
105
|
+
`input_template`, `recipe`) and a `communication` block (`status_line`,
|
|
106
|
+
`recommended_reason`, `human_steps`, `next_expectation`, `must_convey`). These
|
|
107
|
+
are the same meaning in three languages: the schema for machines, `instruction`
|
|
108
|
+
for you, `communication` for what the human hears. They never disagree; if a
|
|
109
|
+
host renders only one lane, `instruction` still tells you what to do.
|
|
110
|
+
|
|
111
|
+
Two `input_template` details matter: entries whose `placeholders` field lists
|
|
112
|
+
unresolved values are NOT executable — fill them and submit via `--input-json
|
|
113
|
+
<file>`; and when `next` is `null`, the honest `input_template`/`recipe` path
|
|
114
|
+
in `interaction` is the continuation — do not invent a direct command.
|
|
115
|
+
|
|
64
116
|
Never print raw envelopes, commands, internal IDs, error classes, or technical
|
|
65
117
|
diagnostics to the human. Explain the service result and the next human choice
|
|
66
118
|
in ordinary language. When a boundary is unclear, load one topic only:
|