@1aboveio/skills 0.19.1 → 0.19.3
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/package.json +1 -1
- package/runtime/skills/distribution/generated/recipes.json +17 -17
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
- package/skills/cicd-pipeline/mergify/references/watch-contract.md +22 -4
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +115 -14
- package/skills/engineering/engineering-runtime/coherence/workflow.json +17 -17
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +13 -13
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +1 -1
- package/skills/engineering/rush-issues/SKILL.md +6 -2
- package/skills/engineering/rush-issues/references/review.md +29 -5
- package/skills/engineering/rush-issues/references/task-plan.md +5 -1
- package/skills/payment/payment-analysis/SKILL.md +8 -4
- package/skills/payment/payment-analysis/references/chargebacks.md +43 -18
- package/skills/payment/payment-analysis/references/event-layers.md +4 -0
- package/skills/payment/payment-analysis/references/metrics.md +9 -7
- package/skills/payment/payment-analysis/references/report-template.md +3 -3
- package/skills/payment/payment-analysis/references/terminology.md +6 -1
- package/skills/payment/payment-analysis/references/visualization.md +1 -1
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"id": "first-party",
|
|
9
9
|
"type": "first-party",
|
|
10
10
|
"package": "@1aboveio/skills",
|
|
11
|
-
"version": "0.19.
|
|
11
|
+
"version": "0.19.3",
|
|
12
12
|
"updatePolicy": "pinned-npm-version",
|
|
13
13
|
"members": [
|
|
14
14
|
{
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
"sourceId": "first-party",
|
|
209
209
|
"sourceType": "first-party",
|
|
210
210
|
"package": "@1aboveio/skills",
|
|
211
|
-
"version": "0.19.
|
|
211
|
+
"version": "0.19.3",
|
|
212
212
|
"installPath": null,
|
|
213
213
|
"members": [
|
|
214
214
|
"harness-runtime",
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
"sourceId": "first-party",
|
|
245
245
|
"sourceType": "first-party",
|
|
246
246
|
"package": "@1aboveio/skills",
|
|
247
|
-
"version": "0.19.
|
|
247
|
+
"version": "0.19.3",
|
|
248
248
|
"installPath": null,
|
|
249
249
|
"members": [
|
|
250
250
|
"harness-runtime",
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
"sourceId": "first-party",
|
|
291
291
|
"sourceType": "first-party",
|
|
292
292
|
"package": "@1aboveio/skills",
|
|
293
|
-
"version": "0.19.
|
|
293
|
+
"version": "0.19.3",
|
|
294
294
|
"installPath": null,
|
|
295
295
|
"members": [
|
|
296
296
|
"harness-runtime",
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
"sourceId": "first-party",
|
|
324
324
|
"sourceType": "first-party",
|
|
325
325
|
"package": "@1aboveio/skills",
|
|
326
|
-
"version": "0.19.
|
|
326
|
+
"version": "0.19.3",
|
|
327
327
|
"installPath": null,
|
|
328
328
|
"members": [
|
|
329
329
|
"harness-runtime",
|
|
@@ -366,7 +366,7 @@
|
|
|
366
366
|
"sourceId": "first-party",
|
|
367
367
|
"sourceType": "first-party",
|
|
368
368
|
"package": "@1aboveio/skills",
|
|
369
|
-
"version": "0.19.
|
|
369
|
+
"version": "0.19.3",
|
|
370
370
|
"installPath": null,
|
|
371
371
|
"members": [
|
|
372
372
|
"harness-runtime",
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
"sourceId": "first-party",
|
|
395
395
|
"sourceType": "first-party",
|
|
396
396
|
"package": "@1aboveio/skills",
|
|
397
|
-
"version": "0.19.
|
|
397
|
+
"version": "0.19.3",
|
|
398
398
|
"installPath": null,
|
|
399
399
|
"members": [
|
|
400
400
|
"harness-runtime",
|
|
@@ -432,7 +432,7 @@
|
|
|
432
432
|
"sourceId": "first-party",
|
|
433
433
|
"sourceType": "first-party",
|
|
434
434
|
"package": "@1aboveio/skills",
|
|
435
|
-
"version": "0.19.
|
|
435
|
+
"version": "0.19.3",
|
|
436
436
|
"installPath": null,
|
|
437
437
|
"members": [
|
|
438
438
|
"harness-runtime",
|
|
@@ -460,7 +460,7 @@
|
|
|
460
460
|
"sourceId": "first-party",
|
|
461
461
|
"sourceType": "first-party",
|
|
462
462
|
"package": "@1aboveio/skills",
|
|
463
|
-
"version": "0.19.
|
|
463
|
+
"version": "0.19.3",
|
|
464
464
|
"installPath": null,
|
|
465
465
|
"members": [
|
|
466
466
|
"harness-runtime",
|
|
@@ -498,7 +498,7 @@
|
|
|
498
498
|
"sourceId": "first-party",
|
|
499
499
|
"sourceType": "first-party",
|
|
500
500
|
"package": "@1aboveio/skills",
|
|
501
|
-
"version": "0.19.
|
|
501
|
+
"version": "0.19.3",
|
|
502
502
|
"installPath": null,
|
|
503
503
|
"members": [
|
|
504
504
|
"harness-runtime",
|
|
@@ -526,7 +526,7 @@
|
|
|
526
526
|
"sourceId": "first-party",
|
|
527
527
|
"sourceType": "first-party",
|
|
528
528
|
"package": "@1aboveio/skills",
|
|
529
|
-
"version": "0.19.
|
|
529
|
+
"version": "0.19.3",
|
|
530
530
|
"installPath": null,
|
|
531
531
|
"members": [
|
|
532
532
|
"harness-runtime",
|
|
@@ -564,7 +564,7 @@
|
|
|
564
564
|
"sourceId": "first-party",
|
|
565
565
|
"sourceType": "first-party",
|
|
566
566
|
"package": "@1aboveio/skills",
|
|
567
|
-
"version": "0.19.
|
|
567
|
+
"version": "0.19.3",
|
|
568
568
|
"installPath": null,
|
|
569
569
|
"members": [
|
|
570
570
|
"harness-runtime",
|
|
@@ -596,7 +596,7 @@
|
|
|
596
596
|
"sourceId": "first-party",
|
|
597
597
|
"sourceType": "first-party",
|
|
598
598
|
"package": "@1aboveio/skills",
|
|
599
|
-
"version": "0.19.
|
|
599
|
+
"version": "0.19.3",
|
|
600
600
|
"installPath": null,
|
|
601
601
|
"members": [
|
|
602
602
|
"harness-runtime",
|
|
@@ -636,7 +636,7 @@
|
|
|
636
636
|
"sourceId": "first-party",
|
|
637
637
|
"sourceType": "first-party",
|
|
638
638
|
"package": "@1aboveio/skills",
|
|
639
|
-
"version": "0.19.
|
|
639
|
+
"version": "0.19.3",
|
|
640
640
|
"installPath": null,
|
|
641
641
|
"members": [
|
|
642
642
|
"harness-runtime"
|
|
@@ -662,7 +662,7 @@
|
|
|
662
662
|
"sourceId": "first-party",
|
|
663
663
|
"sourceType": "first-party",
|
|
664
664
|
"package": "@1aboveio/skills",
|
|
665
|
-
"version": "0.19.
|
|
665
|
+
"version": "0.19.3",
|
|
666
666
|
"installPath": null,
|
|
667
667
|
"members": [
|
|
668
668
|
"harness-runtime"
|
|
@@ -694,7 +694,7 @@
|
|
|
694
694
|
"sourceId": "first-party",
|
|
695
695
|
"sourceType": "first-party",
|
|
696
696
|
"package": "@1aboveio/skills",
|
|
697
|
-
"version": "0.19.
|
|
697
|
+
"version": "0.19.3",
|
|
698
698
|
"installPath": null,
|
|
699
699
|
"members": [
|
|
700
700
|
"harness-runtime",
|
|
@@ -749,7 +749,7 @@
|
|
|
749
749
|
"sourceId": "first-party",
|
|
750
750
|
"sourceType": "first-party",
|
|
751
751
|
"package": "@1aboveio/skills",
|
|
752
|
-
"version": "0.19.
|
|
752
|
+
"version": "0.19.3",
|
|
753
753
|
"installPath": null,
|
|
754
754
|
"members": [
|
|
755
755
|
"harness-runtime",
|
|
@@ -20,12 +20,17 @@ The identical observation means opposite things on either side of queue entry:
|
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| active queue membership absent | `NOT_YET_QUEUED` — healthy, keep waiting | **`DEQUEUED`** — terminal, read the reason |
|
|
22
22
|
|
|
23
|
-
So the watcher's memory *is* the verdict.
|
|
23
|
+
So the watcher's memory *is* the verdict. Five pieces of state must survive across polls:
|
|
24
24
|
|
|
25
25
|
1. **`seenQueued`** — has queue entry ever been observed? Every downstream transition depends on it. Set from the `queued` label **or** `mergify queue show`.
|
|
26
26
|
2. **A re-read counter** (`missingActiveReads` / `absentStreak`) — one absence is suspicion, two consecutive *readable* absences settle `DEQUEUED`.
|
|
27
27
|
3. **Readable vs. absent** — a failed `gh`/`mergify` call is *no information*: it must not advance that counter or overwrite that source's last readable contribution to the stall-state snapshot. During a partial outage, the still-readable sibling remains informative and genuine progress from it still resets the change clock.
|
|
28
28
|
4. **A change clock** (`lastChangeMs`) — the stall bound is *no observable change*, not elapsed time.
|
|
29
|
+
5. **The last synthetic batch PR number** — retain
|
|
30
|
+
`mergeability_check.queue_pull_request_number` from every readable active queue
|
|
31
|
+
observation (and accept the older top-level spelling). Mergify removes that relationship
|
|
32
|
+
from `queue show` after dequeue, but the terminal reason belongs to the synthetic batch,
|
|
33
|
+
not the original PR.
|
|
29
34
|
|
|
30
35
|
### Why a per-poll predicate cannot satisfy this
|
|
31
36
|
|
|
@@ -149,14 +154,24 @@ Suggested routing classes:
|
|
|
149
154
|
|
|
150
155
|
| raw reason shape | route |
|
|
151
156
|
|---|---|
|
|
152
|
-
| `checks-failed
|
|
157
|
+
| batch check `FAILURE`, `ERROR`, or `TIMED_OUT` | `checks-failed`; fix round, fresh verdict, new enqueue |
|
|
158
|
+
| batch checks cancelled before validation executes | `checks-cancelled`; report the cancelled checks and hand back so an unchanged-SHA requeue can be considered |
|
|
153
159
|
| `pull-request-updated`, `draft-pull-request-changed` | HEAD moved; verdict stale; re-enter delivery after fresh validation |
|
|
154
160
|
| `conflict`, `conflict-with-base-branch` | rebase/evidence refresh, then enqueue |
|
|
155
161
|
| `conditions-unmet`, `frozen`, `manual`, `pr-manually-dequeued`, `checks-timeout` | hand back with the condition/reason quoted |
|
|
156
|
-
| unknown | hand back
|
|
162
|
+
| unknown | `unknown`; hand back, fail closed, and preserve `rawReason` |
|
|
157
163
|
|
|
158
164
|
When the reason is not machine-readable, quote the Mergify status comment, queue show condition leaf, or event-log URL in `rawReason` rather than inventing a reason.
|
|
159
165
|
|
|
166
|
+
The selected `state:"dequeued"` Mergify status comment is parsed as structured evidence:
|
|
167
|
+
`dequeuedAtMs`, `rawReason`, its draft PR fallback, and provider-reported failing checks. On
|
|
168
|
+
dequeue, the watcher queries the retained batch PR once and attaches its failed or cancelled
|
|
169
|
+
`statusCheckRollup` entries to the terminal verdict. A missing or deleted batch PR remains a
|
|
170
|
+
truthful `DEQUEUED`: `batchPr.readable` is false, the retained number and `rawReason` survive,
|
|
171
|
+
and any checks named by the provider comment remain available. An unchanged-SHA requeue can
|
|
172
|
+
produce a new synthetic PR; every readable active observation replaces the retained number,
|
|
173
|
+
so the newest batch wins over stale comment fallback.
|
|
174
|
+
|
|
160
175
|
## Watcher Requirements
|
|
161
176
|
|
|
162
177
|
- Run as one background task or one script invocation; do not spend foreground turns polling.
|
|
@@ -195,13 +210,16 @@ Output streams:
|
|
|
195
210
|
| stdout | exactly one pretty-printed terminal JSON verdict, at exit — carrying `phases`, `seenQueued`, and the outcome |
|
|
196
211
|
| stderr | one compact JSON record per poll (`progress: true`), with the live outcome, PR labels, queue membership/position, `phases`, `changed`, and `sinceChangeMs` — the stall clock made visible |
|
|
197
212
|
|
|
198
|
-
Each subprocess read (`gh pr view`, `mergify queue show`) also carries its own bounded
|
|
213
|
+
Each regular-poll subprocess read (`gh pr view`, `mergify queue show`) also carries its own bounded
|
|
199
214
|
timeout — a small multiple of `--interval-seconds`, clamped to never exceed the **effective
|
|
200
215
|
phase stall bound** (pre-admission: `--admission-stall-minutes`; post-entry: `--stall-minutes`)
|
|
201
216
|
capped by `--max-minutes`, then divided by the number of reads made per poll (2 here:
|
|
202
217
|
`gh pr view` + `mergify queue show`). The two reads together cannot outlive the bound that will
|
|
203
218
|
actually fire `STALLED` on this poll — so a hung pre-admission poll cannot outlive the short
|
|
204
219
|
admission clock, and a post-entry poll is not permanently clamped to the admission value.
|
|
220
|
+
After a terminal dequeue classification, one additional bounded `gh pr view` reads the
|
|
221
|
+
retained synthetic batch PR. It enriches the already-settled verdict and never participates
|
|
222
|
+
in queue membership, the absence counter, or the stall clock.
|
|
205
223
|
Those stall/ceiling bounds are only evaluated after a poll returns, and a single hung read
|
|
206
224
|
cannot alone consume more than half of a small stall/ceiling bound. (The sibling watcher makes
|
|
207
225
|
one read per poll, so its clamp divides by 1 and the single-read guarantee is the whole bound
|
|
@@ -162,6 +162,7 @@ export function observationKey({ pr = {}, queue = {} } = {}) {
|
|
|
162
162
|
queue.queued ?? null,
|
|
163
163
|
queue.queueState ?? queue.state ?? null,
|
|
164
164
|
queue.position ?? null,
|
|
165
|
+
queue.batchPrNumber ?? null,
|
|
165
166
|
])
|
|
166
167
|
}
|
|
167
168
|
|
|
@@ -181,6 +182,7 @@ function stallQueueSnapshot(queue = {}) {
|
|
|
181
182
|
queued: queue.queued ?? null,
|
|
182
183
|
queueState: queue.queueState ?? queue.state ?? null,
|
|
183
184
|
position: queue.position ?? null,
|
|
185
|
+
batchPrNumber: queue.batchPrNumber ?? null,
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -413,13 +415,16 @@ export function summarizeStatusChecks(rollup) {
|
|
|
413
415
|
// (`startedMs`) — a locally-slow clock could in principle make a stale dequeue read as fresh;
|
|
414
416
|
// low risk on an NTP-synced host, called out here rather than silently assumed away (NOTE-2).
|
|
415
417
|
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
418
|
+
// Parse the selected dequeue comment once and retain all provider evidence used by the
|
|
419
|
+
// terminal verdict. The rendered comment is also the only surviving source after Mergify's
|
|
420
|
+
// queue API drops active membership and its synthetic PR relationship.
|
|
419
421
|
const MERGIFY_PAYLOAD_RE = /-\*- Mergify Payload -\*-\s*\n(\{[\s\S]*?\})\s*\n-\*- Mergify Payload End -\*-/
|
|
420
422
|
const LEFT_QUEUE_RE = /Left the queue\*\*[^`\n]*`(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}) UTC`/g
|
|
423
|
+
const BATCH_PR_RE = /\bon draft #(\d+)\b/i
|
|
424
|
+
const REASON_RE = /^## Reason\s*\n+([^\n]+)/m
|
|
425
|
+
const PROVIDER_CHECK_RE = /^-\s*(❌|🟠|🛑)\s*\[([^\]]+)\]\(([^)]+)\)/gmu
|
|
421
426
|
|
|
422
|
-
export function
|
|
427
|
+
export function dequeueInfoFromComments(comments) {
|
|
423
428
|
const list = Array.isArray(comments) ? comments : []
|
|
424
429
|
const mergifyComments = list.filter((c) => c && c.author && c.author.login === 'mergify')
|
|
425
430
|
let target = null
|
|
@@ -431,15 +436,36 @@ export function dequeuedAtFromComments(comments) {
|
|
|
431
436
|
try { payload = JSON.parse(payloadMatch[1]) } catch { continue }
|
|
432
437
|
if (payload && payload.state === 'dequeued') target = comment // last match wins — comments are chronological
|
|
433
438
|
}
|
|
434
|
-
if (!target) return null
|
|
439
|
+
if (!target) return { dequeuedAtMs: null, rawReason: null, batchPrNumber: null, providerFailedChecks: [] }
|
|
435
440
|
const body = typeof target.body === 'string' ? target.body : ''
|
|
436
441
|
let match
|
|
437
442
|
let last = null
|
|
438
443
|
LEFT_QUEUE_RE.lastIndex = 0
|
|
439
444
|
while ((match = LEFT_QUEUE_RE.exec(body))) last = match
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
445
|
+
const dequeuedAtMs = last
|
|
446
|
+
? Date.UTC(Number(last[1]), Number(last[2]) - 1, Number(last[3]), Number(last[4]), Number(last[5]))
|
|
447
|
+
: null
|
|
448
|
+
const reasonMatch = REASON_RE.exec(body)
|
|
449
|
+
const batchMatch = BATCH_PR_RE.exec(body)
|
|
450
|
+
const providerFailedChecks = []
|
|
451
|
+
PROVIDER_CHECK_RE.lastIndex = 0
|
|
452
|
+
while ((match = PROVIDER_CHECK_RE.exec(body))) {
|
|
453
|
+
providerFailedChecks.push({
|
|
454
|
+
name: match[2],
|
|
455
|
+
status: match[1] === '🟠' ? 'CANCELLED' : (match[1] === '🛑' ? 'TIMED_OUT' : 'FAILURE'),
|
|
456
|
+
detailsUrl: match[3] || null,
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
dequeuedAtMs,
|
|
461
|
+
rawReason: reasonMatch?.[1]?.trim() || null,
|
|
462
|
+
batchPrNumber: batchMatch ? Number(batchMatch[1]) : null,
|
|
463
|
+
providerFailedChecks,
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function dequeuedAtFromComments(comments) {
|
|
468
|
+
return dequeueInfoFromComments(comments).dequeuedAtMs
|
|
443
469
|
}
|
|
444
470
|
|
|
445
471
|
// Exported so tests can pin that the live read timeout uses the same phase-aware bound the
|
|
@@ -470,6 +496,7 @@ function prObservation({ repo, pr, intervalSeconds, stallMinutes, admissionStall
|
|
|
470
496
|
const data = parseJson(result.stdout)
|
|
471
497
|
if (!data) return { readable: false, error: 'gh pr view returned invalid JSON' }
|
|
472
498
|
const checks = summarizeStatusChecks(data.statusCheckRollup)
|
|
499
|
+
const dequeueInfo = dequeueInfoFromComments(data.comments)
|
|
473
500
|
return {
|
|
474
501
|
readable: true,
|
|
475
502
|
state: data.state || null,
|
|
@@ -478,7 +505,8 @@ function prObservation({ repo, pr, intervalSeconds, stallMinutes, admissionStall
|
|
|
478
505
|
headRefOid: data.headRefOid || null,
|
|
479
506
|
labels: (data.labels || []).map((label) => label.name).filter(Boolean),
|
|
480
507
|
url: data.url || null,
|
|
481
|
-
dequeuedAtMs:
|
|
508
|
+
dequeuedAtMs: dequeueInfo.dequeuedAtMs,
|
|
509
|
+
dequeueInfo,
|
|
482
510
|
...checks,
|
|
483
511
|
}
|
|
484
512
|
}
|
|
@@ -514,12 +542,46 @@ export function queueObservation({ pr, repo, intervalSeconds, stallMinutes, admi
|
|
|
514
542
|
queued: parsed.queued === true || Boolean(parsed.queue_rule_name || parsed.queue_rule),
|
|
515
543
|
queueState: parsed.status || parsed.state || parsed.mergeability_check?.state || null,
|
|
516
544
|
position: typeof parsed.position === 'number' ? parsed.position : null,
|
|
545
|
+
batchPrNumber: positivePrNumber(
|
|
546
|
+
parsed.mergeability_check?.queue_pull_request_number
|
|
547
|
+
?? parsed.queue_pull_request_number
|
|
548
|
+
?? parsed.speculative_check_pr,
|
|
549
|
+
),
|
|
517
550
|
raw: parsed,
|
|
518
551
|
}
|
|
519
552
|
}
|
|
520
553
|
const text = `${result.stdout}\n${result.stderr}`.trim()
|
|
521
|
-
if (/not in the merge queue|not queued|not found/i.test(text)) return { readable: true, queued: false, queueState: 'not-queued', position: null, raw: text }
|
|
522
|
-
return { readable: false, queued: false, queueState: null, position: null, raw: text || 'mergify queue show failed' }
|
|
554
|
+
if (/not in the merge queue|not queued|not found/i.test(text)) return { readable: true, queued: false, queueState: 'not-queued', position: null, batchPrNumber: null, raw: text }
|
|
555
|
+
return { readable: false, queued: false, queueState: null, position: null, batchPrNumber: null, raw: text || 'mergify queue show failed' }
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function positivePrNumber(value) {
|
|
559
|
+
const number = Number(value)
|
|
560
|
+
return Number.isInteger(number) && number > 0 ? number : null
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// The batch relationship is only queried after a terminal dequeue verdict, so normal polls
|
|
564
|
+
// remain the documented two reads. This one-shot enrichment cannot influence lifecycle state.
|
|
565
|
+
function batchPrObservation({ repo, batchPrNumber, intervalSeconds, stallMinutes, admissionStallMinutes, maxMinutes, everActive }) {
|
|
566
|
+
const result = run('gh', ['pr', 'view', String(batchPrNumber), '--repo', repo, '--json', 'state,url,statusCheckRollup'], { timeout: readTimeoutForArgs({ intervalSeconds, stallMinutes, admissionStallMinutes, maxMinutes, everActive }) })
|
|
567
|
+
if (!result.ok) return { readable: false, failedChecks: [], error: result.stderr || result.stdout }
|
|
568
|
+
const data = parseJson(result.stdout)
|
|
569
|
+
if (!data) return { readable: false, failedChecks: [], error: 'gh pr view returned invalid JSON for synthetic batch PR' }
|
|
570
|
+
return { readable: true, state: data.state || null, url: data.url || null, ...summarizeStatusChecks(data.statusCheckRollup) }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function classifyDequeue({ rawReason = null, failedChecks = [] } = {}) {
|
|
574
|
+
const statuses = new Set((Array.isArray(failedChecks) ? failedChecks : []).map((check) => String(check?.status || '').toUpperCase()))
|
|
575
|
+
if (['FAILURE', 'ERROR', 'TIMED_OUT', 'ACTION_REQUIRED', 'STARTUP_FAILURE'].some((status) => statuses.has(status))) return 'checks-failed'
|
|
576
|
+
if (statuses.has('CANCELLED')) return 'checks-cancelled'
|
|
577
|
+
const raw = String(rawReason || '').trim().toLowerCase()
|
|
578
|
+
if (/failing checks|checks failed/.test(raw)) return 'checks-failed'
|
|
579
|
+
if (/pull[- ]request.*updated|draft[- ]pull[- ]request.*changed/.test(raw)) return 'pull-request-updated'
|
|
580
|
+
if (/conflict/.test(raw)) return 'conflict'
|
|
581
|
+
if (/conditions[- ]unmet/.test(raw)) return 'conditions-unmet'
|
|
582
|
+
if (/frozen/.test(raw)) return 'frozen'
|
|
583
|
+
if (/manual|manually dequeued|dequeued by .*\bdequeue\b.*command/.test(raw)) return 'manual'
|
|
584
|
+
return 'unknown'
|
|
523
585
|
}
|
|
524
586
|
|
|
525
587
|
// Every poll's observation, as one compact NDJSON record — the watcher's heartbeat.
|
|
@@ -657,6 +719,7 @@ export async function runCli(argv) {
|
|
|
657
719
|
let lastChangeMs = startedMs
|
|
658
720
|
let observations = 0
|
|
659
721
|
let phases = []
|
|
722
|
+
let lastBatchPrNumber = null
|
|
660
723
|
|
|
661
724
|
for (;;) {
|
|
662
725
|
// Pass everActive into the reads so their per-child timeout tracks the phase bound that
|
|
@@ -666,6 +729,9 @@ export async function runCli(argv) {
|
|
|
666
729
|
const pr = prObservation({ ...args, everActive })
|
|
667
730
|
const queue = queueObservation({ ...args, everActive })
|
|
668
731
|
observations += 1
|
|
732
|
+
if (queue.readable !== false && queue.queued === true && queue.batchPrNumber) {
|
|
733
|
+
lastBatchPrNumber = queue.batchPrNumber
|
|
734
|
+
}
|
|
669
735
|
const active = (Array.isArray(pr.labels) && pr.labels.includes('queued')) || queue.queued === true
|
|
670
736
|
const readable = pr.readable !== false || queue.readable !== false
|
|
671
737
|
if (active) {
|
|
@@ -687,16 +753,37 @@ export async function runCli(argv) {
|
|
|
687
753
|
if (verdict.seenQueued === true) seenQueued = true
|
|
688
754
|
if (verdict.preAdmission === true) phases = nextPhases(phases, 'NOT_YET_QUEUED')
|
|
689
755
|
phases = nextPhases(phases, verdict.outcome)
|
|
756
|
+
let dequeue = null
|
|
757
|
+
if (verdict.outcome === 'DEQUEUED') {
|
|
758
|
+
const info = pr.dequeueInfo || { rawReason: null, batchPrNumber: null, providerFailedChecks: [] }
|
|
759
|
+
const batchPrNumber = lastBatchPrNumber ?? info.batchPrNumber ?? null
|
|
760
|
+
const batch = batchPrNumber ? batchPrObservation({ ...args, everActive, batchPrNumber }) : null
|
|
761
|
+
const failedChecks = batch?.readable && batch.failedChecks.length > 0
|
|
762
|
+
? batch.failedChecks
|
|
763
|
+
: (info.providerFailedChecks || [])
|
|
764
|
+
const classifiedReason = classifyDequeue({ rawReason: info.rawReason, failedChecks })
|
|
765
|
+
dequeue = {
|
|
766
|
+
reason: classifiedReason === 'unknown' && !info.rawReason && failedChecks.length === 0
|
|
767
|
+
? verdict.reason
|
|
768
|
+
: classifiedReason,
|
|
769
|
+
rawReason: info.rawReason || null,
|
|
770
|
+
batchPrNumber,
|
|
771
|
+
failedChecks,
|
|
772
|
+
batch,
|
|
773
|
+
}
|
|
774
|
+
}
|
|
690
775
|
const payload = {
|
|
691
776
|
outcome: verdict.outcome,
|
|
692
777
|
terminal: verdict.terminal,
|
|
693
|
-
reason: verdict.reason || null,
|
|
778
|
+
reason: dequeue?.reason || verdict.reason || null,
|
|
779
|
+
rawReason: dequeue?.rawReason || null,
|
|
694
780
|
detail: verdict.detail || null,
|
|
695
781
|
mergedSha: verdict.mergedSha || null,
|
|
782
|
+
batchPrNumber: dequeue?.batchPrNumber || lastBatchPrNumber,
|
|
696
783
|
seenQueued,
|
|
697
784
|
everActive,
|
|
698
785
|
phases,
|
|
699
|
-
failedChecks: verdict.failedChecks || pr.failedChecks || [],
|
|
786
|
+
failedChecks: dequeue?.failedChecks || verdict.failedChecks || pr.failedChecks || [],
|
|
700
787
|
missingActiveReads,
|
|
701
788
|
observations,
|
|
702
789
|
elapsedMs: Date.now() - startedMs,
|
|
@@ -711,7 +798,21 @@ export async function runCli(argv) {
|
|
|
711
798
|
failedChecks: pr.failedChecks || [],
|
|
712
799
|
pendingChecks: pr.pendingChecks || [],
|
|
713
800
|
},
|
|
714
|
-
queue: {
|
|
801
|
+
queue: {
|
|
802
|
+
queued: queue.queued === true,
|
|
803
|
+
queueState: queue.queueState || null,
|
|
804
|
+
position: queue.position ?? null,
|
|
805
|
+
batchPrNumber: queue.batchPrNumber || null,
|
|
806
|
+
lastBatchPrNumber,
|
|
807
|
+
readable: queue.readable !== false,
|
|
808
|
+
},
|
|
809
|
+
batchPr: dequeue?.batch ? {
|
|
810
|
+
number: dequeue.batchPrNumber,
|
|
811
|
+
state: dequeue.batch.state || null,
|
|
812
|
+
url: dequeue.batch.url || null,
|
|
813
|
+
readable: dequeue.batch.readable !== false,
|
|
814
|
+
error: dequeue.batch.error || null,
|
|
815
|
+
} : null,
|
|
715
816
|
}
|
|
716
817
|
|
|
717
818
|
if (verdict.terminal) {
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
"id": "first-party",
|
|
384
384
|
"type": "first-party",
|
|
385
385
|
"package": "@1aboveio/skills",
|
|
386
|
-
"version": "0.19.
|
|
386
|
+
"version": "0.19.3"
|
|
387
387
|
},
|
|
388
388
|
"contentDigest": "eff6c7b5931bce5b2265a619bccddd371a89df6ce7bc2edc74f11d00060a71dd",
|
|
389
389
|
"digestExcludes": []
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
"id": "first-party",
|
|
397
397
|
"type": "first-party",
|
|
398
398
|
"package": "@1aboveio/skills",
|
|
399
|
-
"version": "0.19.
|
|
399
|
+
"version": "0.19.3"
|
|
400
400
|
},
|
|
401
401
|
"contentDigest": "d97abeff7bdc5ae2e4be2f631d09dba46d4753ce1e67334793364329af59f066",
|
|
402
402
|
"digestExcludes": []
|
|
@@ -409,7 +409,7 @@
|
|
|
409
409
|
"id": "first-party",
|
|
410
410
|
"type": "first-party",
|
|
411
411
|
"package": "@1aboveio/skills",
|
|
412
|
-
"version": "0.19.
|
|
412
|
+
"version": "0.19.3"
|
|
413
413
|
},
|
|
414
414
|
"contentDigest": "00cfdd3e5bfbc3e37b1369ad93aae91161e671829760a8867ea9be25047ba461",
|
|
415
415
|
"digestExcludes": []
|
|
@@ -422,7 +422,7 @@
|
|
|
422
422
|
"id": "first-party",
|
|
423
423
|
"type": "first-party",
|
|
424
424
|
"package": "@1aboveio/skills",
|
|
425
|
-
"version": "0.19.
|
|
425
|
+
"version": "0.19.3"
|
|
426
426
|
},
|
|
427
427
|
"contentDigest": "58b0556228a9271cf9e33727a05fc08a4fdfd29512ec0936b169f0a55d60e6ef",
|
|
428
428
|
"digestExcludes": []
|
|
@@ -435,9 +435,9 @@
|
|
|
435
435
|
"id": "first-party",
|
|
436
436
|
"type": "first-party",
|
|
437
437
|
"package": "@1aboveio/skills",
|
|
438
|
-
"version": "0.19.
|
|
438
|
+
"version": "0.19.3"
|
|
439
439
|
},
|
|
440
|
-
"contentDigest": "
|
|
440
|
+
"contentDigest": "4664873230f22de2fcf46b7b529570cfedabdc850620031d8f96baf89fcdc5b4",
|
|
441
441
|
"digestExcludes": []
|
|
442
442
|
},
|
|
443
443
|
{
|
|
@@ -448,9 +448,9 @@
|
|
|
448
448
|
"id": "first-party",
|
|
449
449
|
"type": "first-party",
|
|
450
450
|
"package": "@1aboveio/skills",
|
|
451
|
-
"version": "0.19.
|
|
451
|
+
"version": "0.19.3"
|
|
452
452
|
},
|
|
453
|
-
"contentDigest": "
|
|
453
|
+
"contentDigest": "3016f025b4acf3b7f61628ff3043ca55e680d0fabc7cf9fb4b4c46424d9b22ed",
|
|
454
454
|
"digestExcludes": []
|
|
455
455
|
},
|
|
456
456
|
{
|
|
@@ -461,7 +461,7 @@
|
|
|
461
461
|
"id": "first-party",
|
|
462
462
|
"type": "first-party",
|
|
463
463
|
"package": "@1aboveio/skills",
|
|
464
|
-
"version": "0.19.
|
|
464
|
+
"version": "0.19.3"
|
|
465
465
|
},
|
|
466
466
|
"contentDigest": "01c192d8b4fd416af4ea02e8000e5fce0cee723a6ac2f8c0c48372087e1a262e",
|
|
467
467
|
"digestExcludes": []
|
|
@@ -474,7 +474,7 @@
|
|
|
474
474
|
"id": "first-party",
|
|
475
475
|
"type": "first-party",
|
|
476
476
|
"package": "@1aboveio/skills",
|
|
477
|
-
"version": "0.19.
|
|
477
|
+
"version": "0.19.3"
|
|
478
478
|
},
|
|
479
479
|
"contentDigest": "ffba0c83491676794c74721f4c6dbae430f5bc186e5a55f0764e29eaeb13829b",
|
|
480
480
|
"digestExcludes": []
|
|
@@ -487,7 +487,7 @@
|
|
|
487
487
|
"id": "first-party",
|
|
488
488
|
"type": "first-party",
|
|
489
489
|
"package": "@1aboveio/skills",
|
|
490
|
-
"version": "0.19.
|
|
490
|
+
"version": "0.19.3"
|
|
491
491
|
},
|
|
492
492
|
"contentDigest": "f048fd00c69f2dc666fc7a3096cfeee6dfba73933bfb69602f3f0e26159798cc",
|
|
493
493
|
"digestExcludes": []
|
|
@@ -500,7 +500,7 @@
|
|
|
500
500
|
"id": "first-party",
|
|
501
501
|
"type": "first-party",
|
|
502
502
|
"package": "@1aboveio/skills",
|
|
503
|
-
"version": "0.19.
|
|
503
|
+
"version": "0.19.3"
|
|
504
504
|
},
|
|
505
505
|
"contentDigest": "90a7c4e1ad6da1e632ea2c5259e4967ffebb84353adccbca8dfc5d6bc50b60c1",
|
|
506
506
|
"digestExcludes": []
|
|
@@ -513,15 +513,15 @@
|
|
|
513
513
|
"id": "first-party",
|
|
514
514
|
"type": "first-party",
|
|
515
515
|
"package": "@1aboveio/skills",
|
|
516
|
-
"version": "0.19.
|
|
516
|
+
"version": "0.19.3"
|
|
517
517
|
},
|
|
518
|
-
"contentDigest": "
|
|
518
|
+
"contentDigest": "6ef7cf062d899835774d3010dbba511587b0d60835447417d4b74a5afe25b184",
|
|
519
519
|
"digestExcludes": [
|
|
520
520
|
"coherence/workflow.json"
|
|
521
521
|
]
|
|
522
522
|
}
|
|
523
523
|
],
|
|
524
|
-
"releaseIdentity": "
|
|
524
|
+
"releaseIdentity": "63593db8810d932c5774b375f3403578b821c9df6ca6e8a866f12f2b8fc4fd47",
|
|
525
525
|
"lifecycleAuthority": "native-skills-cli",
|
|
526
526
|
"repairRecipe": {
|
|
527
527
|
"id": "engineering-workflow-dependency-first",
|
|
@@ -579,7 +579,7 @@
|
|
|
579
579
|
"sourceId": "first-party",
|
|
580
580
|
"sourceType": "first-party",
|
|
581
581
|
"package": "@1aboveio/skills",
|
|
582
|
-
"version": "0.19.
|
|
582
|
+
"version": "0.19.3",
|
|
583
583
|
"installPath": null,
|
|
584
584
|
"members": [
|
|
585
585
|
"harness-runtime",
|
|
@@ -597,7 +597,7 @@
|
|
|
597
597
|
"commands": [
|
|
598
598
|
{
|
|
599
599
|
"transport": "npm",
|
|
600
|
-
"command": "npx @1aboveio/skills@0.19.
|
|
600
|
+
"command": "npx @1aboveio/skills@0.19.3 install --group engineering-workflow --yes"
|
|
601
601
|
}
|
|
602
602
|
],
|
|
603
603
|
"onFailure": {
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
"id": "first-party",
|
|
238
238
|
"type": "first-party",
|
|
239
239
|
"package": "@1aboveio/skills",
|
|
240
|
-
"version": "0.19.
|
|
240
|
+
"version": "0.19.3"
|
|
241
241
|
}
|
|
242
242
|
},
|
|
243
243
|
{
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"id": "first-party",
|
|
249
249
|
"type": "first-party",
|
|
250
250
|
"package": "@1aboveio/skills",
|
|
251
|
-
"version": "0.19.
|
|
251
|
+
"version": "0.19.3"
|
|
252
252
|
}
|
|
253
253
|
},
|
|
254
254
|
{
|
|
@@ -259,7 +259,7 @@
|
|
|
259
259
|
"id": "first-party",
|
|
260
260
|
"type": "first-party",
|
|
261
261
|
"package": "@1aboveio/skills",
|
|
262
|
-
"version": "0.19.
|
|
262
|
+
"version": "0.19.3"
|
|
263
263
|
}
|
|
264
264
|
},
|
|
265
265
|
{
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
"id": "first-party",
|
|
271
271
|
"type": "first-party",
|
|
272
272
|
"package": "@1aboveio/skills",
|
|
273
|
-
"version": "0.19.
|
|
273
|
+
"version": "0.19.3"
|
|
274
274
|
}
|
|
275
275
|
},
|
|
276
276
|
{
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
"id": "first-party",
|
|
282
282
|
"type": "first-party",
|
|
283
283
|
"package": "@1aboveio/skills",
|
|
284
|
-
"version": "0.19.
|
|
284
|
+
"version": "0.19.3"
|
|
285
285
|
}
|
|
286
286
|
},
|
|
287
287
|
{
|
|
@@ -292,7 +292,7 @@
|
|
|
292
292
|
"id": "first-party",
|
|
293
293
|
"type": "first-party",
|
|
294
294
|
"package": "@1aboveio/skills",
|
|
295
|
-
"version": "0.19.
|
|
295
|
+
"version": "0.19.3"
|
|
296
296
|
}
|
|
297
297
|
},
|
|
298
298
|
{
|
|
@@ -303,7 +303,7 @@
|
|
|
303
303
|
"id": "first-party",
|
|
304
304
|
"type": "first-party",
|
|
305
305
|
"package": "@1aboveio/skills",
|
|
306
|
-
"version": "0.19.
|
|
306
|
+
"version": "0.19.3"
|
|
307
307
|
}
|
|
308
308
|
},
|
|
309
309
|
{
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
"id": "first-party",
|
|
315
315
|
"type": "first-party",
|
|
316
316
|
"package": "@1aboveio/skills",
|
|
317
|
-
"version": "0.19.
|
|
317
|
+
"version": "0.19.3"
|
|
318
318
|
}
|
|
319
319
|
},
|
|
320
320
|
{
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
"id": "first-party",
|
|
326
326
|
"type": "first-party",
|
|
327
327
|
"package": "@1aboveio/skills",
|
|
328
|
-
"version": "0.19.
|
|
328
|
+
"version": "0.19.3"
|
|
329
329
|
}
|
|
330
330
|
},
|
|
331
331
|
{
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
"id": "first-party",
|
|
337
337
|
"type": "first-party",
|
|
338
338
|
"package": "@1aboveio/skills",
|
|
339
|
-
"version": "0.19.
|
|
339
|
+
"version": "0.19.3"
|
|
340
340
|
}
|
|
341
341
|
},
|
|
342
342
|
{
|
|
@@ -347,7 +347,7 @@
|
|
|
347
347
|
"id": "first-party",
|
|
348
348
|
"type": "first-party",
|
|
349
349
|
"package": "@1aboveio/skills",
|
|
350
|
-
"version": "0.19.
|
|
350
|
+
"version": "0.19.3"
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
],
|
|
@@ -468,7 +468,7 @@
|
|
|
468
468
|
"sourceId": "first-party",
|
|
469
469
|
"sourceType": "first-party",
|
|
470
470
|
"package": "@1aboveio/skills",
|
|
471
|
-
"version": "0.19.
|
|
471
|
+
"version": "0.19.3",
|
|
472
472
|
"installPath": null,
|
|
473
473
|
"members": [
|
|
474
474
|
"harness-runtime",
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
"commands": [
|
|
487
487
|
{
|
|
488
488
|
"transport": "npm",
|
|
489
|
-
"command": "npx @1aboveio/skills@0.19.
|
|
489
|
+
"command": "npx @1aboveio/skills@0.19.3 install --group engineering-workflow --yes"
|
|
490
490
|
}
|
|
491
491
|
],
|
|
492
492
|
"onFailure": {
|
|
@@ -3412,7 +3412,7 @@ export const WORKFLOW_PREFLIGHT_COMMANDS = Object.freeze([
|
|
|
3412
3412
|
|
|
3413
3413
|
const WORKFLOW_VERIFIER_URL = new URL('../../engineering-runtime/scripts/workflow-coherence.mjs', import.meta.url)
|
|
3414
3414
|
const WORKFLOW_FALLBACK_POLICY_URL = new URL('../generated/workflow-repair-policy.json', import.meta.url)
|
|
3415
|
-
export const WORKFLOW_TRUSTED_FALLBACK_POLICY_SHA256 = '
|
|
3415
|
+
export const WORKFLOW_TRUSTED_FALLBACK_POLICY_SHA256 = 'b3ae9760db370098e3737c7b23bcfd5e03d61e30586857858826b6875e3b4a2f'
|
|
3416
3416
|
const WORKFLOW_REPAIR_RECIPE_REFERENCE = Object.freeze({
|
|
3417
3417
|
id: 'engineering-workflow-dependency-first',
|
|
3418
3418
|
generatedFrom: 'skills/distribution/generated/recipes.json',
|
|
@@ -72,6 +72,10 @@ before its stage; do not invent an unlinked substitute.
|
|
|
72
72
|
canary and requires validation plus a fresh canary on the new HEAD.
|
|
73
73
|
- Review facts independently. Producer summaries, claims, logs, and evidence
|
|
74
74
|
may locate work but cannot establish correctness for the reviewer.
|
|
75
|
+
- Treat review P0/P1 findings as blocking. P2/P3 findings do not expand the
|
|
76
|
+
current PR, but each must be linked to a concrete follow-up before review
|
|
77
|
+
succeeds; a finding that proves a current AC or mandatory standard is unmet
|
|
78
|
+
must be reclassified as P1, not deferred under a softer label.
|
|
75
79
|
- Do not manufacture review evidence collateral. Keep only the stage result,
|
|
76
80
|
commands actually run, findings, task-plan transitions, and profiling events.
|
|
77
81
|
- Persist each task-plan transition and profiling event under one monotonically
|
|
@@ -144,8 +148,8 @@ the original spec and repository standards, computes its own diff fixed point,
|
|
|
144
148
|
inspects the implementation, and runs checks needed to verify findings. It does
|
|
145
149
|
not accept producer evidence as proof. Fix blocking findings on the combined
|
|
146
150
|
branch, validate, rerun the warehouse canary when required, then run a fresh
|
|
147
|
-
review until
|
|
148
|
-
with a revised approach.
|
|
151
|
+
review until every P0/P1 is resolved and every P2/P3 has a tracked follow-up;
|
|
152
|
+
after 2 failed fix/review cycles, diagnose and continue with a revised approach.
|
|
149
153
|
|
|
150
154
|
### 7. CICD
|
|
151
155
|
|
|
@@ -24,17 +24,41 @@ new evidence collateral as a substitute for verification.
|
|
|
24
24
|
for the current exact HEAD. Then invoke the absolute `code-review` skill path
|
|
25
25
|
on the combined HEAD.
|
|
26
26
|
2. Review both axes: repository **Standards** and original **Spec**.
|
|
27
|
+
Treat `code-review` as a barrier: buffer individual Standards and Spec
|
|
28
|
+
completions, and do not classify, defer, or fix any finding until both axes
|
|
29
|
+
have returned and one aggregated review result exists.
|
|
27
30
|
3. Independently run or reproduce the checks needed to support each material
|
|
28
31
|
conclusion. A producer-provided green log is not a factual substitute.
|
|
29
|
-
4. Return findings with severity and file/line references, plus commands
|
|
30
|
-
reviewer actually ran. The review result is coordination state, not an
|
|
32
|
+
4. Return findings with P0-P3 severity and file/line references, plus commands
|
|
33
|
+
the reviewer actually ran. The review result is coordination state, not an
|
|
31
34
|
evidence-generation deliverable.
|
|
32
|
-
5.
|
|
33
|
-
|
|
35
|
+
5. Apply the severity/disposition policy below. Succeed only when every finding
|
|
36
|
+
has an explicit resolved or deferred disposition.
|
|
37
|
+
|
|
38
|
+
## Severity and disposition
|
|
39
|
+
|
|
40
|
+
- **P0/P1 are blocking.** Fix them on the combined branch, validate, rerun the
|
|
41
|
+
required canary, and obtain a fresh review before recording `reviewedHead`.
|
|
42
|
+
- **P2/P3 are non-blocking by default.** Do not expand the current PR to address
|
|
43
|
+
them. Create or link a concrete follow-up issue before review succeeds and
|
|
44
|
+
record its URL with the finding.
|
|
45
|
+
- **No finding disappears.** Each finding is either `resolved` on the exact
|
|
46
|
+
reviewed HEAD or `deferred` with a follow-up URL. A prose note without a
|
|
47
|
+
trackable issue is not a disposition.
|
|
48
|
+
- **Severity cannot waive the current contract.** If a purported P2/P3 proves
|
|
49
|
+
that a current acceptance criterion is unmet, required behavior is wrong, or
|
|
50
|
+
a mandatory repository standard or gate is violated, ask the reviewer to
|
|
51
|
+
reclassify it as P1. Do not silently override the reviewer and do not defer it
|
|
52
|
+
under the softer label.
|
|
53
|
+
|
|
54
|
+
P2/P3 follow-ups are appropriate for optional hardening, cleanup, cosmetic
|
|
55
|
+
improvements, and architecture work outside the current acceptance criteria.
|
|
56
|
+
Link each follow-up to the combined PR and original spec, and deduplicate it
|
|
57
|
+
against existing issues before creating a new one.
|
|
34
58
|
|
|
35
59
|
## Fix loop
|
|
36
60
|
|
|
37
|
-
For
|
|
61
|
+
For P0/P1 findings, fix the combined PR branch, run relevant lint/build/
|
|
38
62
|
test/smoke, rerun [canary.md](canary.md) when required, then spawn a fresh
|
|
39
63
|
review of the new HEAD. Record that exact SHA as `reviewedHead` only after
|
|
40
64
|
success. Never treat a fix as accepted before validation, required canary, and
|
|
@@ -47,7 +47,11 @@ Run-level fields include `planRevision`, models/efforts, `targetBranch`, run
|
|
|
47
47
|
workspace/branch, original spec locator, explicit merge authorization when one
|
|
48
48
|
was supplied, exploration path, combined PR/HEAD, `reviewedHead`, and
|
|
49
49
|
`warehouseCanaryRequired`, its classification reason, durable canary result
|
|
50
|
-
path, and start/update timestamps.
|
|
50
|
+
path, `reviewFindings`, and start/update timestamps. Each `reviewFindings` entry
|
|
51
|
+
records a stable finding id, P0-P3 `severity`, summary and source locator,
|
|
52
|
+
`status` (`open`, `resolved`, or `deferred`), the exact `resolvedHead` when
|
|
53
|
+
resolved, and `followUpUrl` when deferred. P0/P1 cannot be deferred; P2/P3
|
|
54
|
+
cannot remain open when `reviewedHead` is recorded.
|
|
51
55
|
|
|
52
56
|
Persist the machine-readable plan at:
|
|
53
57
|
|
|
@@ -5,9 +5,10 @@ description: >
|
|
|
5
5
|
extracts (optional chargebacks): executive summary, top statistics, payment
|
|
6
6
|
journey Sankey, and topics (volume, WoW trend, auth rate, BIN-country
|
|
7
7
|
contribution, decline-by-reason, decline-by-country). Amounts report in USD
|
|
8
|
-
(Forex Service daily conversion). Chargebacks
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
(Forex Service daily conversion). Chargebacks separate normal and
|
|
9
|
+
pre-dispute cases; Visa pre-disputes are excluded from VAMP-style metrics
|
|
10
|
+
and calculated separately, while Mastercard 拒付率 is lagged. 运营影响 is
|
|
11
|
+
3DS / 风控拦截 / soft-decline retry only. Use whenever the user asks for payment overview, auth-rate analysis,
|
|
11
12
|
decline mix, BIN/issuer-country contribution, settlement GMV, payment journey,
|
|
12
13
|
Sankey funnel, FX-to-USD, chargeback rate, 运营影响, or "business analysis of
|
|
13
14
|
transactions" — even if they do not say payment-analysis. Do NOT use for
|
|
@@ -57,7 +58,8 @@ Compute auth + settlement metrics from [metrics.md](references/metrics.md).
|
|
|
57
58
|
Convert non-USD amounts per [fx.md](references/fx.md) before any sum or share.
|
|
58
59
|
If chargebacks are present, monthly 拒付率 follows
|
|
59
60
|
[chargebacks.md](references/chargebacks.md) (Visa same-month, Mastercard lagged,
|
|
60
|
-
|
|
61
|
+
Visa pre-disputes excluded from the normal/VAMP-style numerator and calculated
|
|
62
|
+
separately, skip empty brand-months, no blended Visa+Mastercard ratio).
|
|
61
63
|
|
|
62
64
|
### 3 — Journey Sankey
|
|
63
65
|
|
|
@@ -98,6 +100,8 @@ Do not change numbers, joins, or chart data.
|
|
|
98
100
|
- [ ] Amount axes/legends include currency (`授权尝试金额 USD`, …)
|
|
99
101
|
- [ ] No supervised fraud-rule metrics
|
|
100
102
|
- [ ] Non-USD amounts converted per fx.md; all-USD books say 金额均为 USD
|
|
103
|
+
- [ ] Normal chargebacks, pre-disputes, and unclassified cases are distinct
|
|
104
|
+
- [ ] Visa pre-disputes are excluded from VAMP-style metrics and calculated separately
|
|
101
105
|
- [ ] Chargeback monthly rates skip empty brand-months; no blended Visa+MC ratio
|
|
102
106
|
- [ ] 运营影响 only from auth-rate-actions.md when triggered
|
|
103
107
|
- [ ] Language matches terminology.md; banned strings have no unexplained hits
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Chargeback / dispute rate (Visa vs Mastercard)
|
|
1
|
+
# Chargeback / dispute rate (normal vs pre-dispute; Visa vs Mastercard)
|
|
2
2
|
|
|
3
3
|
Topic **4.8 拒付 / 争议**. Put the Visa / Mastercard formulas as a **note
|
|
4
4
|
under the monthly rate chart**. Count rates follow card-network monitoring
|
|
@@ -10,43 +10,67 @@ below are the processor restatements used in production monitoring
|
|
|
10
10
|
(Stripe, Braintree). Caption the report as **network-style estimates
|
|
11
11
|
from this extract**, not as a Visa/Mastercard identification notice.
|
|
12
12
|
|
|
13
|
+
## Classify case stage first
|
|
14
|
+
|
|
15
|
+
Before calculating rates, classify each row from an explicit processor or
|
|
16
|
+
network case-stage/type/status field:
|
|
17
|
+
|
|
18
|
+
- **Normal chargeback / dispute**: a formally opened chargeback/dispute.
|
|
19
|
+
- **Pre-dispute**: explicitly identified as pre-dispute, RDR, CDRN, early
|
|
20
|
+
resolution, or the processor's equivalent pre-dispute product.
|
|
21
|
+
- **Unclassified**: the extract does not reliably identify the case stage.
|
|
22
|
+
|
|
23
|
+
Use the processor's data dictionary when labels are unclear. Do not infer
|
|
24
|
+
pre-dispute status from reason code, amount, timing, outcome, or free-text
|
|
25
|
+
description alone. Report counts and amounts for all three classes separately;
|
|
26
|
+
never silently treat pre-dispute or unclassified rows as normal chargebacks.
|
|
27
|
+
|
|
28
|
+
If no reliable classification field exists, report the separation as
|
|
29
|
+
unavailable. An all-case Visa dispute rate may be shown as descriptive only,
|
|
30
|
+
but it must not be called a normal chargeback rate or VAMP-style estimate.
|
|
31
|
+
|
|
13
32
|
## When a point is drawn
|
|
14
33
|
|
|
15
|
-
A monthly point for a
|
|
34
|
+
A monthly point for a brand and case-stage series is drawn **only if**
|
|
16
35
|
|
|
17
|
-
- numerator
|
|
36
|
+
- numerator for that specific series **received** in that calendar month > 0, and
|
|
18
37
|
- denominator (sales count for that brand, month as defined below) > 0.
|
|
19
38
|
|
|
20
39
|
Otherwise **omit the point**. Do not plot `0`. Do not invent a prior-month
|
|
21
|
-
sale count. If a
|
|
22
|
-
|
|
23
|
-
exist).
|
|
40
|
+
sale count. If a series has no drawable points, omit it. If no series remain,
|
|
41
|
+
omit the rate chart (keep case-stage and reason-category bars if cases exist).
|
|
24
42
|
|
|
25
43
|
## Formulas (count)
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
count every CB row.
|
|
45
|
+
Numerators use cases **received** in calendar month `M` (`Chargeback date`),
|
|
46
|
+
`Payment method` = that brand. Do not use original transaction month. Apply
|
|
47
|
+
the case-stage classification before counting.
|
|
31
48
|
|
|
32
|
-
| Brand | Programme
|
|
49
|
+
| Brand / series | Programme alignment | Rate |
|
|
33
50
|
|---|---|---|
|
|
34
|
-
| Visa | VAMP dispute/sales
|
|
35
|
-
|
|
|
51
|
+
| Visa normal chargeback | VAMP dispute/sales timing (same month) | `Normal_CB_Visa(M) / Sales_Visa(M)` |
|
|
52
|
+
| Visa pre-dispute | Descriptive; excluded from VAMP | `PreDispute_Visa(M) / Sales_Visa(M)` |
|
|
53
|
+
| Mastercard normal chargeback | ECP / ECM chargeback timing (lagged sales) | `Normal_CB_MC(M) / Sales_MC(M−1)` |
|
|
36
54
|
|
|
37
55
|
`Sales_*` = settlement type Sale, that brand, **transaction/capture month**.
|
|
38
56
|
Do not mix refunds into the denominator.
|
|
39
57
|
|
|
40
58
|
Mastercard **must** use the previous calendar month’s sales. If `M−1`
|
|
41
|
-
sales are missing from the extract, skip Mastercard for month `M`.
|
|
59
|
+
sales are missing from the extract, skip Mastercard for month `M`. Keep any
|
|
60
|
+
Mastercard pre-dispute rows separate from its normal chargeback numerator; this
|
|
61
|
+
skill does not calculate a Mastercard pre-dispute rate unless the user asks.
|
|
42
62
|
|
|
43
63
|
## Visa notes (do not over-claim VAMP)
|
|
44
64
|
|
|
45
65
|
Since 2025-04 Visa folded VDMP/VFMP into **VAMP**. Full VAMP Count is
|
|
46
66
|
`opened Visa chargebacks (TC15) + reported fraud (TC40 EFW)`, same-month
|
|
47
|
-
sales in the denominator.
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
sales in the denominator. **Pre-dispute cases are not opened chargebacks and
|
|
68
|
+
must be excluded from the TC15/VAMP-style numerator.** This skill's extract
|
|
69
|
+
usually has chargebacks only → report **Visa normal 拒付率 = normal Visa CB
|
|
70
|
+
count / Visa same-month sales**, plus **Visa pre-dispute rate = Visa
|
|
71
|
+
pre-dispute count / Visa same-month sales** as a separate descriptive series.
|
|
72
|
+
Do not add the two series together. Do not label the normal rate “VAMP ratio”
|
|
73
|
+
unless TC40/EFW is present and the extract supports the full VAMP definition.
|
|
50
74
|
|
|
51
75
|
VAMP also has volume and enumeration legs; those are out of scope here.
|
|
52
76
|
|
|
@@ -62,7 +86,8 @@ it for ECP.
|
|
|
62
86
|
## Amount rate (optional footnote)
|
|
63
87
|
|
|
64
88
|
`abs(dispute amount) / sale amount` with the **same month alignment as
|
|
65
|
-
the count rate** for that brand.
|
|
89
|
+
the count rate** for that brand. Keep normal and pre-dispute amounts separate.
|
|
90
|
+
Secondary; the line chart is count %.
|
|
66
91
|
|
|
67
92
|
## Window caveat
|
|
68
93
|
|
|
@@ -53,6 +53,10 @@ Reporting amounts are USD. Conversion rules: [fx.md](fx.md).
|
|
|
53
53
|
Chargeback **programme 拒付率** uses received month (`Chargeback date`) per
|
|
54
54
|
[chargebacks.md](chargebacks.md). If you also show origination-month incidence,
|
|
55
55
|
label that separately so it is not mistaken for the Visa/Mastercard rate.
|
|
56
|
+
Before calculating a programme-style rate, identify the processor field that
|
|
57
|
+
distinguishes normal chargebacks from pre-disputes. Keep unclassified cases
|
|
58
|
+
separate; pre-disputes are excluded from Visa VAMP-style metrics and reported
|
|
59
|
+
as a separate Visa rate.
|
|
56
60
|
|
|
57
61
|
## Decline taxonomy (auth)
|
|
58
62
|
|
|
@@ -38,18 +38,20 @@ Do not equalize settled sales to approved auth amount without a timing caveat.
|
|
|
38
38
|
|
|
39
39
|
## Chargebacks
|
|
40
40
|
|
|
41
|
-
Monthly **拒付率** by brand is [chargebacks.md](chargebacks.md):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
Monthly **拒付率** by brand is [chargebacks.md](chargebacks.md): classify normal,
|
|
42
|
+
pre-dispute, and unclassified cases first; Visa uses same-month sales and
|
|
43
|
+
reports pre-disputes separately; Mastercard normal chargebacks use
|
|
44
|
+
previous-month sales. Skip empty brand-months and do not blend Visa+Mastercard.
|
|
45
|
+
The table below is mix / match, not that programme rate.
|
|
45
46
|
|
|
46
47
|
| KPI | Formula | Notes |
|
|
47
48
|
|---|---|---|
|
|
48
|
-
|
|
|
49
|
+
| Normal / pre-dispute / unclassified count and amount | as classified | Amounts USD after [fx.md](fx.md); use abs for magnitude if signed |
|
|
49
50
|
| Reason mix | by Reason category / code | Fraud ≠ all CBs |
|
|
50
51
|
| Match rate to auths | matched / CB rows | State join key |
|
|
51
|
-
| Visa monthly 拒付率 |
|
|
52
|
-
|
|
|
52
|
+
| Visa normal monthly 拒付率 | Normal_CB_Visa(M) / Sales_Visa(M) | Excludes pre-disputes; received month; not VAMP unless TC40 is present |
|
|
53
|
+
| Visa pre-dispute rate | PreDispute_Visa(M) / Sales_Visa(M) | Separate descriptive series; excluded from VAMP-style numerator |
|
|
54
|
+
| Mastercard normal monthly 拒付率 | Normal_CB_MC(M) / Sales_MC(M−1) | Excludes pre-disputes; skip M if M−1 sales missing |
|
|
53
55
|
|
|
54
56
|
Lag: chargeback date is later than original auth. Programme rates use
|
|
55
57
|
**received** month, not origination vintage.
|
|
@@ -54,8 +54,8 @@ KPI cards / table from auth + settlement: attempts, approvals, auth rate,
|
|
|
54
54
|
attempt amount, approved amount, sale count/amount, refund count/amount, net
|
|
55
55
|
settled, distinct PAN/BIN/countries, portfolio constants (MID, MCC, provider).
|
|
56
56
|
|
|
57
|
-
Chinese labels: 授权尝试, 授权成功, 授权成功率, 授权成功金额,
|
|
58
|
-
净清算, 发卡行国家.
|
|
57
|
+
Chinese labels: 授权尝试, 授权成功, 授权成功率, 授权成功金额,
|
|
58
|
+
已清算交易笔数, 已清算金额, 退款, 净清算, 发卡行国家.
|
|
59
59
|
|
|
60
60
|
### 3. Payment journey (Sankey) / 支付旅程
|
|
61
61
|
|
|
@@ -79,7 +79,7 @@ Each topic: short lede + chart(s) + supporting table.
|
|
|
79
79
|
| 4.5 Decline reason | count bars |
|
|
80
80
|
| 4.6 Decline by country | stacked count bars |
|
|
81
81
|
| 4.7 Settlements | sale/refund amount bars; country contribution dual axis |
|
|
82
|
-
| 4.8 Chargebacks | reason-category bars; Visa
|
|
82
|
+
| 4.8 Chargebacks | normal vs pre-dispute counts/amounts; reason-category bars; Visa normal 拒付率 and separate Visa pre-dispute-rate series; Mastercard normal monthly 拒付率 per [chargebacks.md](chargebacks.md) (note under the rate chart; skip empty brand-months) |
|
|
83
83
|
|
|
84
84
|
Use both authorization and settlement data when both files are present.
|
|
85
85
|
|
|
@@ -24,7 +24,11 @@ writing English or Chinese copy. After writing, run the proofread step in
|
|
|
24
24
|
| BIN country | 发卡行国家 / BIN 国家 | 客户国家;收货国;发卡国 |
|
|
25
25
|
| Settlement | 清算 | |
|
|
26
26
|
| Sale / Refund | 销售 / 退款 | 把销售金额写成清算金额 |
|
|
27
|
+
| Settled transaction count | 已清算交易笔数 | 清算销售 |
|
|
28
|
+
| Settled amount | 已清算金额 | 清算销售金额 |
|
|
27
29
|
| Chargeback / dispute | 拒付 / 争议 | 与清算混用 |
|
|
30
|
+
| Normal chargeback / dispute | 正常拒付 / 正常争议 | 与预争议合并 |
|
|
31
|
+
| Pre-dispute | 预争议 | 计入 VAMP;与正常拒付合并 |
|
|
28
32
|
| Clerical | 差错 | 文书;文书错误 |
|
|
29
33
|
| Quality | 产品服务质量 | 质量;质量争议 |
|
|
30
34
|
| Incidence | 发生率(必须写出分母) | |
|
|
@@ -37,7 +41,8 @@ writing English or Chinese copy. After writing, run the proofread step in
|
|
|
37
41
|
| This analysis window | 当前分析时段内 | 本窗口 |
|
|
38
42
|
| SCA | SCA | 强客户认证(可在首次括注后只用 SCA) |
|
|
39
43
|
| Authentication / 3DS | 鉴权 / 认证 / 3DS | 把 3DS 叫转化 |
|
|
40
|
-
| Visa /
|
|
44
|
+
| Visa normal 拒付率 / Visa pre-dispute rate | Visa 正常拒付率 / Visa 预争议率 | 合并二者;把预争议计入 VAMP;VAMP ratio(无 TC40 时) |
|
|
45
|
+
| Mastercard normal 拒付率 | Mastercard 正常拒付率 | ECM(从本抽取断言商户在项目中) |
|
|
41
46
|
| Amounts in USD | 金额均为 USD | 未做汇率折算(当全部已是 USD) |
|
|
42
47
|
| Count-heavy / amount-light | 授权尝试笔数多但金额小 | 走廊;笔数负担型走廊 |
|
|
43
48
|
|
|
@@ -22,7 +22,7 @@ ECharts (Sankey). Markdown tables remain required beside charts.
|
|
|
22
22
|
## Axis hygiene
|
|
23
23
|
|
|
24
24
|
- Label every axis with the full metric name. Amount axes/legends include
|
|
25
|
-
currency (`授权尝试金额 USD`, `授权拒绝金额 USD`,
|
|
25
|
+
currency (`授权尝试金额 USD`, `授权拒绝金额 USD`, `已清算金额 USD`,
|
|
26
26
|
`拒付金额 USD`). Do not use bare `笔数` / `金额` / `%`.
|
|
27
27
|
- Only one axis draws the main grid on dual-axis charts.
|
|
28
28
|
- Stacked bars = mix within a group (e.g. decline reasons by country), never rate.
|