Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

### Fixed

- **draft/update 47001**:`create_wechat_draft --update` 复用了 draft/add 的 `articles` 数组形状,而 draft/update 要求单篇文章对象,微信返回 47001 data format error(relay 现场 A/B 复现:数组 → 47001,对象 → ok);新增 `applyDraftUpdateShape` 统一转换,harness 测试钉住形状
- **Pipeline 自洽修补**:显式串联 render/preflight/orchestrator 的封面图和 `--skip-image-check` 参数,避免 `pre_image_missing` 隐式破坏默认入口
- **AutoHeal 阻断逻辑**:preflight JSON 改为完整解析,存在不可自动修复的 L1/Agent 失败时不再继续进入 bundle
- **Bundle/relay 契约**:封面图纳入 bundle,relay 目录日期由本地生成,远程推送命令正确传递标题、作者、封面和 `--crop-235-1`
Expand Down
35 changes: 35 additions & 0 deletions harness/test-draft-update-shape.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import { applyDraftUpdateShape } from "../scripts/create_wechat_draft.mjs";

// draft/update requires `articles` as a single article object, while
// draft/add uses an array. Reusing the array shape yields WeChat errcode
// 47001 (data format error) -- verified live on 2026-07-17 against the
// XINZHE account (array -> 47001, object -> ok).

const payload = { articles: [{ title: "T", author: "A", content: "<p>x</p>" }] };
const shaped = applyDraftUpdateShape(payload, "MID123", "2");
assert.equal(shaped, payload, "returns the same payload for convenience");
assert.equal(payload.media_id, "MID123");
assert.equal(payload.index, 2);
assert.equal(Array.isArray(payload.articles), false, "articles must become a single object");
assert.equal(payload.articles.title, "T");
assert.equal(payload.articles.author, "A");

// Already-object payloads pass through untouched.
const already = { articles: { title: "T2" } };
applyDraftUpdateShape(already, "MID9", "0");
assert.equal(already.articles.title, "T2");
assert.equal(already.index, 0);

// Missing or invalid index falls back to 0.
const fallback = { articles: [{ title: "T3" }] };
applyDraftUpdateShape(fallback, "MID5", "not-a-number");
assert.equal(fallback.index, 0);

// Call-site contract: callers pass a shallow copy so downstream bookkeeping
// (push-result, audit) keeps reading plan.payload.articles[0] as an array.
const src = { articles: [{ title: "T4" }] };
applyDraftUpdateShape({ ...src }, "MID7", "0");
assert.equal(Array.isArray(src.articles), true, "original payload keeps array shape");

console.log("test-draft-update-shape: ok");
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"render": "node scripts/render_wechat_editorial.mjs",
"publish": "node scripts/create_wechat_draft.mjs",
"relay:check": "node scripts/sync_relay_scripts.mjs --check --json",
"check": "find scripts harness -name '*.mjs' -print | sort | xargs -n1 node --check && node harness/test-code-generator-contract.mjs && node harness/test-preflight-fixtures.mjs && node harness/test-orchestrator-command-contract.mjs && node harness/test-relay-script-sync-contract.mjs && node harness/test-publish-evidence-contract.mjs && node harness/test-reconcile-wechat-drafts.mjs && node harness/test-self-report-no-write.mjs && node harness/run-generated-check-tests.mjs"
"check": "find scripts harness -name '*.mjs' -print | sort | xargs -n1 node --check && node harness/test-code-generator-contract.mjs && node harness/test-preflight-fixtures.mjs && node harness/test-orchestrator-command-contract.mjs && node harness/test-relay-script-sync-contract.mjs && node harness/test-publish-evidence-contract.mjs && node harness/test-draft-update-shape.mjs && node harness/test-reconcile-wechat-drafts.mjs && node harness/test-self-report-no-write.mjs && node harness/run-generated-check-tests.mjs"
},
"engines": {
"node": ">=18.0.0"
Expand Down
20 changes: 17 additions & 3 deletions scripts/create_wechat_draft.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ import { parseArgs, printHelp, requireArg } from "./lib/memory-lib.mjs";
const stableTokenEndpoint = "https://api.weixin.qq.com/cgi-bin/stable_token";
const draftAddEndpoint = "https://api.weixin.qq.com/cgi-bin/draft/add";
const draftUpdateEndpoint = "https://api.weixin.qq.com/cgi-bin/draft/update";

export function applyDraftUpdateShape(payload, updateMediaId, updateIndex) {
// draft/add expects `articles` as an array, but draft/update requires a
// single article object. Reusing the add shape for update fails with
// WeChat errcode 47001 (data format error). Mutates the given payload;
// callers should pass a shallow copy so downstream bookkeeping keeps the
// original array shape. Returns the payload for convenience.
payload.media_id = String(updateMediaId);
payload.index = parseInt(updateIndex, 10) || 0;
if (Array.isArray(payload.articles)) {
payload.articles = payload.articles[0];
}
return payload;
}
const imageAddEndpoint = "https://api.weixin.qq.com/cgi-bin/material/add_material";
const defaultEnvPath = path.resolve(process.cwd(), ".env");
const targetCoverAspectRatio = 2.35;
Expand Down Expand Up @@ -1531,14 +1545,14 @@ export async function createWechatDraft(argv = process.argv.slice(2)) {

let endpoint = draftAddEndpoint;
const isUpdate = Boolean(plan.updateMediaId);
let requestPayload = plan.payload;
if (isUpdate) {
endpoint = draftUpdateEndpoint;
plan.payload.media_id = plan.updateMediaId;
plan.payload.index = parseInt(plan.updateIndex, 10) || 0;
requestPayload = applyDraftUpdateShape({ ...plan.payload }, plan.updateMediaId, plan.updateIndex);
}

const url = `${endpoint}?access_token=${encodeURIComponent(accessToken)}`;
const result = postJson(url, plan.payload);
const result = postJson(url, requestPayload);

if (result.errcode && result.errcode !== 0) {
const action = isUpdate ? "draft/update" : "draft/add";
Expand Down
Loading