pixivflow 3.4.2 → 3.4.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.
@@ -43,6 +43,7 @@ const node_crypto_1 = require("node:crypto");
43
43
  const node_stream_1 = require("node:stream");
44
44
  const logger_1 = require("../logger");
45
45
  const DeliveryAck_1 = require("./DeliveryAck");
46
+ const refetchProvenance_1 = require("./refetchProvenance");
46
47
  const redact_1 = require("../utils/redact");
47
48
  /** Render an ISO timestamp to YYYY-MM-DD (create_date is JST). */
48
49
  function formatPublishedDate(iso) {
@@ -476,7 +477,10 @@ function buildTemplateVariables(request) {
476
477
  // Remote manual replacement ("重抓") request UUID; empty for scheduled runs.
477
478
  // Carried through the delivery payload context (extraContext) so the
478
479
  // receiving service can correlate the review with its refetch attempt.
479
- refetchRequestId: c.refetchRequestId ?? '',
480
+ // Canonicalized at this last boundary before the wire: the receiver accepts
481
+ // only a canonical dashed lowercase UUID and rejects the whole submission
482
+ // otherwise (see delivery/refetchProvenance.ts).
483
+ refetchRequestId: (0, refetchProvenance_1.canonicalRefetchRequestId)(c.refetchRequestId),
480
484
  };
481
485
  }
482
486
  /**
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The `refetch_request_id` wire contract.
3
+ *
4
+ * The receiving service (TelePost) validates this field as a *canonical* dashed
5
+ * lowercase UUID — `utils/api_server.py:706-715` answers HTTP 400
6
+ * `invalid_refetch_provenance` ("refetch_request_id 必须是 UUID") for anything
7
+ * else — and it rejects the whole submission, permanently, before any review
8
+ * exists. It is strict on purpose: the stored value must later match the
9
+ * refetch attempt's own `request_id`, which TelePost generates as
10
+ * `str(uuid.uuid4())`.
11
+ *
12
+ * Callers hand us their key in whatever spelling they generated it. Production
13
+ * incident 2026-09-27: a manual refetch arrived at the delivery with the bare
14
+ * 32-hex key `1e22b55cb33e47289f30c62e8ee1e11f` — a v4 UUID whose dashes were
15
+ * stripped — and the run ended in
16
+ * `permanent delivery failure: delivery endpoint HTTP 400:
17
+ * {"code":"invalid_refetch_provenance","message":"refetch_request_id 必须是 UUID"}`.
18
+ *
19
+ * So the boundary canonicalizes instead of forwarding the raw key:
20
+ *
21
+ * - a UUID in any spelling we can recognize (dashed, undashed, UPPER, braces,
22
+ * `urn:uuid:` prefix) becomes the canonical dashed lowercase form, which
23
+ * keeps the receiving service's identity match intact;
24
+ * - a value that is not a UUID at all becomes `''` — an unusable provenance
25
+ * must never fail an otherwise valid delivery, and the empty value is
26
+ * already the documented shape of a scheduled (non-refetch) delivery.
27
+ *
28
+ * Identity is never touched here: the consumer's own idempotency key keeps its
29
+ * exact spelling as the durable `manual_request_id` (slot identity), and only
30
+ * the payload field that crosses the wire is normalized.
31
+ */
32
+ /**
33
+ * Canonical dashed lowercase UUID for `value`, or `''` when `value` is not a
34
+ * UUID. Mirrors Python's `uuid.UUID()` leniency exactly: only hyphens may be
35
+ * removed, and what remains must be 32 hex digits.
36
+ */
37
+ export declare function canonicalRefetchRequestId(value: unknown): string;
38
+ //# sourceMappingURL=refetchProvenance.d.ts.map
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * The `refetch_request_id` wire contract.
4
+ *
5
+ * The receiving service (TelePost) validates this field as a *canonical* dashed
6
+ * lowercase UUID — `utils/api_server.py:706-715` answers HTTP 400
7
+ * `invalid_refetch_provenance` ("refetch_request_id 必须是 UUID") for anything
8
+ * else — and it rejects the whole submission, permanently, before any review
9
+ * exists. It is strict on purpose: the stored value must later match the
10
+ * refetch attempt's own `request_id`, which TelePost generates as
11
+ * `str(uuid.uuid4())`.
12
+ *
13
+ * Callers hand us their key in whatever spelling they generated it. Production
14
+ * incident 2026-09-27: a manual refetch arrived at the delivery with the bare
15
+ * 32-hex key `1e22b55cb33e47289f30c62e8ee1e11f` — a v4 UUID whose dashes were
16
+ * stripped — and the run ended in
17
+ * `permanent delivery failure: delivery endpoint HTTP 400:
18
+ * {"code":"invalid_refetch_provenance","message":"refetch_request_id 必须是 UUID"}`.
19
+ *
20
+ * So the boundary canonicalizes instead of forwarding the raw key:
21
+ *
22
+ * - a UUID in any spelling we can recognize (dashed, undashed, UPPER, braces,
23
+ * `urn:uuid:` prefix) becomes the canonical dashed lowercase form, which
24
+ * keeps the receiving service's identity match intact;
25
+ * - a value that is not a UUID at all becomes `''` — an unusable provenance
26
+ * must never fail an otherwise valid delivery, and the empty value is
27
+ * already the documented shape of a scheduled (non-refetch) delivery.
28
+ *
29
+ * Identity is never touched here: the consumer's own idempotency key keeps its
30
+ * exact spelling as the durable `manual_request_id` (slot identity), and only
31
+ * the payload field that crosses the wire is normalized.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.canonicalRefetchRequestId = canonicalRefetchRequestId;
35
+ /**
36
+ * Canonical dashed lowercase UUID for `value`, or `''` when `value` is not a
37
+ * UUID. Mirrors Python's `uuid.UUID()` leniency exactly: only hyphens may be
38
+ * removed, and what remains must be 32 hex digits.
39
+ */
40
+ function canonicalRefetchRequestId(value) {
41
+ if (typeof value !== 'string')
42
+ return '';
43
+ let candidate = value.trim().toLowerCase();
44
+ if (!candidate)
45
+ return '';
46
+ if (candidate.startsWith('urn:uuid:'))
47
+ candidate = candidate.slice('urn:uuid:'.length);
48
+ if (candidate.startsWith('{') && candidate.endsWith('}'))
49
+ candidate = candidate.slice(1, -1);
50
+ const hex = candidate.replace(/-/g, '');
51
+ if (!/^[0-9a-f]{32}$/.test(hex))
52
+ return '';
53
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
54
+ }
55
+ //# sourceMappingURL=refetchProvenance.js.map
@@ -10,7 +10,11 @@ import { TargetConfig } from '../../config';
10
10
  * `refetchRequestId` is the request UUID of a remote manual replacement ("重抓")
11
11
  * that produced this delivery; it is empty for scheduled occurrences. The
12
12
  * receiving service (TelePost) correlates the incoming review with the refetch
13
- * attempt on this id.
13
+ * attempt on this id, and validates it as a canonical UUID — see
14
+ * `delivery/refetchProvenance.ts` for why an unusable key becomes empty instead
15
+ * of failing the whole delivery. The consumer's idempotency key itself keeps its
16
+ * exact spelling as the durable slot identity; only this wire field is
17
+ * normalized.
14
18
  */
15
19
  export declare function deliveryContextFields(target: TargetConfig): Record<string, unknown>;
16
20
  //# sourceMappingURL=deliveryContext.d.ts.map
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deliveryContextFields = deliveryContextFields;
4
+ const logger_1 = require("../../logger");
5
+ const refetchProvenance_1 = require("../../delivery/refetchProvenance");
4
6
  /**
5
7
  * Delivery payload context for one target run, merging schedule/slot provenance
6
8
  * with the optional remote manual replacement identity.
@@ -12,12 +14,34 @@ exports.deliveryContextFields = deliveryContextFields;
12
14
  * `refetchRequestId` is the request UUID of a remote manual replacement ("重抓")
13
15
  * that produced this delivery; it is empty for scheduled occurrences. The
14
16
  * receiving service (TelePost) correlates the incoming review with the refetch
15
- * attempt on this id.
17
+ * attempt on this id, and validates it as a canonical UUID — see
18
+ * `delivery/refetchProvenance.ts` for why an unusable key becomes empty instead
19
+ * of failing the whole delivery. The consumer's idempotency key itself keeps its
20
+ * exact spelling as the durable slot identity; only this wire field is
21
+ * normalized.
16
22
  */
17
23
  function deliveryContextFields(target) {
18
24
  const ec = target.delivery;
19
25
  const slotContext = ec?.slotContext;
20
26
  const executionContext = ec?.executionContext;
27
+ const manualRequestId = slotContext?.manualRequestId ?? '';
28
+ const refetchRequestId = (0, refetchProvenance_1.canonicalRefetchRequestId)(manualRequestId);
29
+ if (manualRequestId && !refetchRequestId) {
30
+ // Visible, not silent: the delivery still goes out (the receiving service
31
+ // treats an empty provenance as "not a refetch"), but the operator must be
32
+ // able to see that this manual run cannot be correlated remotely.
33
+ logger_1.logger.warn('Manual refetch request id is not a UUID; sending an empty refetch_request_id so the delivery is not rejected', {
34
+ slotId: slotContext?.slotId ?? '',
35
+ manualRequestIdLength: manualRequestId.length,
36
+ manualRequestIdPrefix: manualRequestId.slice(0, 8),
37
+ });
38
+ }
39
+ else if (manualRequestId && manualRequestId !== refetchRequestId) {
40
+ logger_1.logger.info('Canonicalized the manual refetch request id for the delivery payload', {
41
+ slotId: slotContext?.slotId ?? '',
42
+ canonicalRefetchRequestId: refetchRequestId,
43
+ });
44
+ }
21
45
  return {
22
46
  scheduleId: executionContext?.scheduleId ?? '',
23
47
  executionId: executionContext?.slotId ?? '',
@@ -26,7 +50,7 @@ function deliveryContextFields(target) {
26
50
  slotId: slotContext?.slotId ?? executionContext?.slotId ?? '',
27
51
  slotName: slotContext?.slotName ?? executionContext?.slotName ?? '',
28
52
  slotDate: slotContext?.slotDate ?? executionContext?.slotDate ?? '',
29
- refetchRequestId: slotContext?.manualRequestId ?? '',
53
+ refetchRequestId,
30
54
  };
31
55
  }
32
56
  //# sourceMappingURL=deliveryContext.js.map
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow",
4
- "version": "3.4.2",
4
+ "version": "3.4.3",
5
5
  "private": true
6
6
  }
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BUILD = void 0;
4
4
  // GENERATED by scripts/write-version.js — do not edit manually.
5
- exports.BUILD = { version: '3.4.2', commit: '4db0bf21a785' };
5
+ exports.BUILD = { version: '3.4.3', commit: '74ffa4d29ec2' };
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "commonjs",
3
3
  "name": "pixivflow-webui-backend",
4
- "version": "3.4.2",
4
+ "version": "3.4.3",
5
5
  "description": "PixivFlow WebUI Backend - CommonJS module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixivflow",
3
- "version": "3.4.2",
3
+ "version": "3.4.3",
4
4
  "description": "🎨 Pixiv 下载、筛选与自动收集工具 - 批量下载插画和小说、按标签/热度/日期筛选、定时任务与可靠 HTTP 交付 | Pixiv downloader and automation toolkit with filtering, scheduling and reliable HTTP delivery",
5
5
  "repository": {
6
6
  "type": "git",