@tea-agent/loop-agent 0.42.0-next.5 → 0.42.0-next.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ - 修复 backend-test correspondence 把前面函数的 `pytest.param(..., id="TP-...")` 叠进后续 Case `parameterIds` 的问题:decorator 区域遇到上一 `def`/`class` 或非 decorator 续行即停,不再把整组打成 `TEST_POINT_BINDING_EXTRA`。eligibility 门禁不放宽。
6
+ - 修复 backend-test scenario facts 把内部 max/min/oversize helper 哨兵 `-1/-2/-3` 写成 `observedLength`、导致 N10 在 schema `min(0)` 上 `invalid-output` 的问题:这些 helper 仍按 Markdown bound MATCH,持久化长度只保留非负值。
7
+ - 修复 backend-test 把 GET `params=`/`query=` 过滤字段、Python `True`/`False` 布尔字面量和混合大小写 `pytest.param` 列表漏出 correspondence/payload/scenario 观察的问题:这些 variant 现在可以按 item 授权,而不再把整 Case 打成 `PAYLOAD_SHAPE_MISMATCH` / `VARIANT_PARAMETER_MISSING`。eligibility 门禁不放宽。
8
+ - 修复 backend-test 计划协议标题被翻译成中文后 N3 直接 `missing-module-index`、以及 Markdown writer 漏写精确 Scenario Partition slot 只能等到 N6 才失败的问题:N3 现在把 `## 模块索引/覆盖范围/覆盖矩阵/场景分区` 确定性改写成英文 canonical heading 并最多写回一次 run-owned `plan.md`,同时展开 slot 清单写入 `contracts/backend-test-scenario-partition-slots.json` 并注入 map child `requiredVariantSlots`。Markdown shard completeness 对缺失精确 `TP-SP-...` variant 报 `incomplete-partition-slot-set` 并允许一次有界补写。覆盖语义按方案 A:Case 数量可以少于枚举数量,但每个 slot 仍须独立 variant Test Point;`SINGLE`/`MULTIPLE` 不计覆盖。N6 fail-closed 不放宽。
9
+ - 修复 backend-test strict Markdown map writer 的 upstream artifact pointer 泄漏:当长上游输出的 `stdout.md` 不在当前节点 `readSet` 时,不再向 Pi 注入不可读取的 artifact 路径,避免模型触发 `read-outside-read-set` 后进入 pending;保留有界 preview,并新增回归覆盖严格 writer 边界。
5
10
  - 修复 backend-test Manifest 把模型生成的 Module Stem 展示单元格当成模块身份真源、导致 low-tier 输出(缺 `test_` 前缀或整段 Markdown 链接)在 N3 `module-markdown-path-mismatch` 反复失败的问题:N3 现在先解析唯一合法 Markdown Path 与 `<stem>.md` 相对链接作为 canonical 身份,再校验业务 ownership/split 与路径安全;Stem 单元格只作冗余展示并能确定性重写为 canonical 文件名 stem。strict `backendTest.moduleLayout` 改为先按唯一 required Markdown Path 定位模块,再恢复冻结 Markdown/Pytest path pair,避免错误 stem 提前丢弃整行,同时保留 Plan-only pytest path 修复。Markdown Path 缺失/多候选/越界或与 strict path pair 冲突、以及多行 canonicalize 后同 identity(`duplicate-canonical-module-stem`)继续 fail-closed,不再静默去重。同步加强 N2 prompt(Stem 单元格仅允许纯文件名 stem)。
6
11
  - 修复 Next Release 的 io-heavy 测试并发不稳定:发布 workflow 将该池固定为单 worker,避免全部测试已通过后触发 Vitest `onTaskUpdate` RPC settlement timeout,并消除 controller identity/package fingerprint 测试之间的共享状态竞态;不跳过或删除任何测试。
7
12
  - 修复 backend-test 唯一 pytest collection repair 已产生受控非空修改、却因模型遗漏 `IMPLEMENTATION_OUTCOME` 而阻断整条执行链的问题:仅 N11 exact repair writer 可把完全缺失的 outcome 确定性恢复为候选 changed,随后仍必须由 N12 重新通过 scenario、correspondence、payload、collection、fixture 和 readiness 门禁;unknown/conflicting/blocked、空 diff、越界写入及 provider/context failure 继续 fail-closed。
@@ -235,6 +240,20 @@
235
240
  - 右侧辅助栏统一承载文件、上下文、调用记录、运行活动和当前 DAG;点击消息流中的运行状态即可在同一侧栏查看运行、门禁与产物,并可自由调整侧栏宽度。
236
241
  - 新增任务清单与结构化提问卡片,让长任务的进度和需要用户选择的内容更容易理解。
237
242
  - 「对话 / 操作 / 观测」使用统一导航;操作和观测页面可以切换工作区查看对应数据。
243
+ ## [0.41.1] - 2026-09-02
244
+
245
+ ### 修复
246
+
247
+ - 修复 Next Release 发布 workflow 的 io-heavy 测试并发不稳定:将该测试池固定为单 worker,避免全部测试通过后因过高并发触发 Vitest `onTaskUpdate` RPC settlement timeout,并消除 controller identity / package fingerprint 测试之间的共享状态竞态。
248
+ - 修复 Operator Chat 旧会话继续使用过期模型认证的问题:模型配置变化后,下一轮发送或手动重新加载会自动刷新模型目录和当前模型认证。
249
+ - 修复 Operator Chat 长工具链的消息错序:模型过程文字、思考和工具卡片现在按服务端事件顺序稳定交替展示,刷新历史会话后也保持相同顺序。
250
+ - 统一工作区、Todo 和其他滚动区域的滚动条样式,默认更轻巧,鼠标悬浮时也更容易辨认。
251
+ - HTML 文件预览现在支持页面内的标签切换等交互;较大的自包含 HTML 可以在新窗口中直接完整加载,不再受源码摘要长度限制。
252
+
253
+ ### 测试
254
+
255
+ - `harness.json` Pi MED 档测试不再 pin 具体模型 id,改为校验 tier 形状,避免每次 dogfood 档位切换都造成测试失败。
256
+
238
257
  ## [0.41.0] - 2026-08-31
239
258
 
240
259
  ### 新增
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.42.0-next.5",
4
- "gitSha": "260dc2585e355412fa689ac856badc73b2f609f9",
5
- "builtAt": "2026-09-01T18:19:10.922Z"
3
+ "version": "0.42.0-next.6",
4
+ "gitSha": "6919b06fcb268f3a61890e85b6bc4dcbf11b62e1",
5
+ "builtAt": "2026-09-02T06:01:45.078Z"
6
6
  }
@@ -4500,6 +4500,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
4500
4500
  ? "pytest-generate"
4501
4501
  : "md-generate",
4502
4502
  writeSet: input.task.writeSet ?? [],
4503
+ runDir: meta.runDir,
4503
4504
  })
4504
4505
  : isBackendTestMdPlanTask(input.task)
4505
4506
  ? await assessBackendTestMdPlanCompleteness(input.cwd, backendLayout)
@@ -1463,7 +1463,7 @@ for top in ast.walk(tree):
1463
1463
  returned_names.add(node.value.id)
1464
1464
  if isinstance(node,ast.Call):
1465
1465
  for keyword in node.keywords:
1466
- if keyword.arg in {'payload','body','request_body','request_json','dto','json','data','json_body'} and isinstance(keyword.value,ast.Name) and keyword.value.id in positional:
1466
+ if keyword.arg in {'payload','body','request_body','request_json','dto','json','data','json_body','params','query','query_params'} and isinstance(keyword.value,ast.Name) and keyword.value.id in positional:
1467
1467
  transport.append((keyword.value.id,positional.index(keyword.value.id)-call_offset))
1468
1468
  if isinstance(node.func,ast.Attribute) and node.func.attr.lower() in {'post','put','patch'} and len(node.args) >= 2:
1469
1469
  candidate=node.args[1]
@@ -1484,7 +1484,7 @@ for top in ast.walk(tree):
1484
1484
  for node in ast.walk(top):
1485
1485
  if not isinstance(node,ast.Call): continue
1486
1486
  for keyword in node.keywords:
1487
- if keyword.arg in {'payload','body','request_body','request_json','dto','json','data','json_body'} and isinstance(keyword.value,ast.Name) and keyword.value.id in assigned:
1487
+ if keyword.arg in {'payload','body','request_body','request_json','dto','json','data','json_body','params','query','query_params'} and isinstance(keyword.value,ast.Name) and keyword.value.id in assigned:
1488
1488
  transport_shapes.append(assigned[keyword.value.id])
1489
1489
  if candidates: FUNCTION_PAYLOADS[top.name]=candidates
1490
1490
  if transport_shapes: FUNCTION_TRANSPORT_SHAPES[top.name]=transport_shapes
@@ -1540,8 +1540,9 @@ def literal(node):
1540
1540
  return repr(node.value)
1541
1541
  return None
1542
1542
 
1543
- def collect_dict(node, prefix, paths, values):
1543
+ def collect_dict(node, prefix, paths, values, parameters=None):
1544
1544
  if not isinstance(node, ast.Dict): return
1545
+ parameters = parameters or {}
1545
1546
  for key,value in zip(node.keys,node.values):
1546
1547
  if not isinstance(key, ast.Constant) or not isinstance(key.value,str): continue
1547
1548
  # Ignore writer helper/meta keys that are not DTO request fields.
@@ -1551,12 +1552,16 @@ def collect_dict(node, prefix, paths, values):
1551
1552
  paths.add(current)
1552
1553
  lv=literal(value)
1553
1554
  if lv is not None: values.setdefault(current,set()).add(lv)
1554
- if isinstance(value, ast.Dict): collect_dict(value,current,paths,values)
1555
+ elif isinstance(value, ast.Name) and value.id in parameters:
1556
+ for bound in parameters[value.id]:
1557
+ bound_lv=literal(bound)
1558
+ if bound_lv is not None: values.setdefault(current,set()).add(bound_lv)
1559
+ if isinstance(value, ast.Dict): collect_dict(value,current,paths,values,parameters)
1555
1560
  elif isinstance(value,(ast.List,ast.Tuple)):
1556
1561
  for item in value.elts:
1557
- if isinstance(item,ast.Dict): collect_dict(item,current,paths,values)
1562
+ if isinstance(item,ast.Dict): collect_dict(item,current,paths,values,parameters)
1558
1563
 
1559
- PAYLOAD_NAMES={'payload','body','request_body','request_json','dto','json','data','json_body'}
1564
+ PAYLOAD_NAMES={'payload','body','request_body','request_json','dto','json','data','json_body','params','query','query_params'}
1560
1565
 
1561
1566
  def shape_only(node):
1562
1567
  if isinstance(node,ast.Dict):
@@ -1637,6 +1642,7 @@ def decorator_payload_nodes(parameters,assigned):
1637
1642
 
1638
1643
  def decorator_payload_nodes_by_tp(fn,assigned):
1639
1644
  result={}
1645
+ function_payload_nodes=payload_nodes(fn)
1640
1646
  for dec in fn.decorator_list:
1641
1647
  if not isinstance(dec,ast.Call) or not isinstance(dec.func,ast.Attribute) or dec.func.attr != 'parametrize' or len(dec.args) < 2: continue
1642
1648
  if not isinstance(dec.args[0],ast.Constant) or not isinstance(dec.args[0].value,str): continue
@@ -1653,9 +1659,11 @@ def decorator_payload_nodes_by_tp(fn,assigned):
1653
1659
  parameters={}
1654
1660
  for index,name in enumerate(names):
1655
1661
  if index < len(node.args): parameters.setdefault(name,[]).append(node.args[index])
1656
- payload_nodes=decorator_payload_nodes(parameters,assigned)
1662
+ payload_nodes_for_tp=decorator_payload_nodes(parameters,assigned)
1663
+ if not payload_nodes_for_tp:
1664
+ payload_nodes_for_tp=function_payload_nodes
1657
1665
  paths=set(); values={}
1658
- for payload_node in payload_nodes: collect_dict(payload_node,'',paths,values)
1666
+ for payload_node in payload_nodes_for_tp: collect_dict(payload_node,'',paths,values,parameters)
1659
1667
  result[tp]={key:sorted(items) for key,items in sorted(values.items())}
1660
1668
  return result
1661
1669
 
@@ -1729,8 +1737,8 @@ for fn in ast.walk(tree):
1729
1737
  if isinstance(fn,(ast.FunctionDef,ast.AsyncFunctionDef)) and fn.name.startswith('test_'):
1730
1738
  paths=set(); values={}
1731
1739
  fallback_paths=set(); fallback_values={}
1732
- for node in payload_nodes(fn): collect_dict(node,'',paths,values)
1733
- for node in fallback_payload_nodes(fn): collect_dict(node,'',fallback_paths,fallback_values)
1740
+ for node in payload_nodes(fn): collect_dict(node,'',paths,values,{})
1741
+ for node in fallback_payload_nodes(fn): collect_dict(node,'',fallback_paths,fallback_values,{})
1734
1742
  out[fn.name]={
1735
1743
  'paths':sorted(paths),
1736
1744
  'values':{key:sorted(items) for key,items in sorted(values.items())},
@@ -1782,14 +1790,39 @@ function metadataTestPoints(region, label) {
1782
1790
  const match = new RegExp(`^\\s*${escaped}\\s*:\\s*(.*)$`, "mi").exec(region);
1783
1791
  return orderedUnique(match?.[1]?.match(/\bTP-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g) ?? []);
1784
1792
  }
1793
+ function isPytestDecoratorOrContinuationLine(line) {
1794
+ const trimmed = line.trim();
1795
+ if (!trimmed)
1796
+ return false;
1797
+ if (trimmed.startsWith("#"))
1798
+ return true;
1799
+ if (trimmed.startsWith("@"))
1800
+ return true;
1801
+ // Multi-line decorator argument lists: @pytest.mark.parametrize(\n "x", [\n pytest.param(..., id="TP-...")\n ]\n)
1802
+ return /^[\]\),]/.test(trimmed) || /[\(\[{,]$/.test(trimmed) || /\bpytest\.param\s*\(/.test(trimmed);
1803
+ }
1785
1804
  function pytestFunctionRegionStart(source, functionIndex) {
1786
1805
  let regionStart = source.lastIndexOf("\n", functionIndex - 1) + 1;
1787
1806
  let cursor = regionStart;
1807
+ let blankRun = 0;
1788
1808
  while (cursor > 0) {
1789
1809
  const previousEnd = cursor - 1;
1790
1810
  const previousStart = source.lastIndexOf("\n", previousEnd - 1) + 1;
1791
1811
  const previous = source.slice(previousStart, previousEnd).replace(/\r$/, "");
1792
- if (!previous.trim())
1812
+ const trimmed = previous.trim();
1813
+ if (!trimmed) {
1814
+ blankRun += 1;
1815
+ // Keep walking across a small blank gap so a decorator is still bound to
1816
+ // the following test when writers insert one empty line before `def`.
1817
+ if (blankRun > 2)
1818
+ break;
1819
+ cursor = previousStart;
1820
+ continue;
1821
+ }
1822
+ blankRun = 0;
1823
+ if (/^(?:async\s+)?def\s+/.test(trimmed) || /^(?:async\s+)?class\s+/.test(trimmed))
1824
+ break;
1825
+ if (!isPytestDecoratorOrContinuationLine(previous))
1793
1826
  break;
1794
1827
  regionStart = previousStart;
1795
1828
  cursor = previousStart;
@@ -1819,12 +1852,14 @@ function pytestFunctionBodyEnd(source, signatureEnd, functionIndent) {
1819
1852
  }
1820
1853
  function pytestParameterCollections(source) {
1821
1854
  const collections = new Map();
1822
- for (const match of source.matchAll(/^(_?[A-Z][A-Z0-9_]*)\s*=\s*\[(?![^\r\n]*\])([\s\S]*?)^\]/gm)) {
1855
+ for (const match of source.matchAll(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\[(?![^\r\n]*\])([\s\S]*?)^\]/gm)) {
1823
1856
  const explicitIds = [...match[2].matchAll(/\bid\s*=\s*["'](TP-[A-Z0-9-]+)["']/g)].map((item) => item[1]);
1824
1857
  const factoryIds = explicitIds.length === 0
1825
1858
  ? [...match[2].matchAll(/["'](TP-[A-Z0-9-]+)["']/g)].map((item) => item[1])
1826
1859
  : [];
1827
- collections.set(match[1], orderedUnique([...explicitIds, ...factoryIds]));
1860
+ const ids = orderedUnique([...explicitIds, ...factoryIds]);
1861
+ if (ids.length > 0)
1862
+ collections.set(match[1], ids);
1828
1863
  }
1829
1864
  return collections;
1830
1865
  }
@@ -1886,6 +1921,12 @@ function normalizePythonLiteralValue(value) {
1886
1921
  if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
1887
1922
  return trimmed.slice(1, -1);
1888
1923
  }
1924
+ if (trimmed === "True")
1925
+ return "true";
1926
+ if (trimmed === "False")
1927
+ return "false";
1928
+ if (trimmed === "None")
1929
+ return "null";
1889
1930
  return trimmed;
1890
1931
  }
1891
1932
  function intentionalInvalidEnumVariant(input) {
@@ -1930,13 +1971,15 @@ function assessPayloadContract(testCase, refs) {
1930
1971
  : [];
1931
1972
  const enumMismatches = [];
1932
1973
  for (const [field, allowed] of Object.entries(testCase.payloadContract.enumValues)) {
1933
- const observed = orderedUnique(refs.flatMap((item) => (useFallback ? item.fallbackPayloadValues[field] : item.payloadValues[field]) ?? []));
1974
+ const aggregated = orderedUnique(refs.flatMap((item) => (useFallback ? item.fallbackPayloadValues[field] : item.payloadValues[field]) ?? []));
1975
+ const byTestPoint = orderedUnique(refs.flatMap((item) => Object.values(item.payloadValuesByTestPoint).flatMap((values) => values[field] ?? [])));
1976
+ const observed = orderedUnique([...aggregated, ...byTestPoint]);
1934
1977
  for (const rawValue of observed) {
1935
1978
  const value = normalizePythonLiteralValue(rawValue);
1936
1979
  if (allowed.includes(value))
1937
1980
  continue;
1938
1981
  const associatedTestPoints = orderedUnique(refs.flatMap((item) => Object.entries(item.payloadValuesByTestPoint)
1939
- .filter(([, values]) => (values[field] ?? []).includes(rawValue))
1982
+ .filter(([, values]) => (values[field] ?? []).some((itemValue) => itemValue === rawValue || normalizePythonLiteralValue(itemValue) === value))
1940
1983
  .map(([testPoint]) => testPoint)));
1941
1984
  const intentionallyInvalid = associatedTestPoints.length > 0 && associatedTestPoints.every((testPoint) => intentionalInvalidEnumVariant({ testCase, testPoint, field, observedValue: rawValue, allowed }));
1942
1985
  if (!intentionallyInvalid)
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Deterministic backend-test plan protocol helpers.
3
+ *
4
+ * English canonical `##` headings remain the parse authority. Known Chinese
5
+ * aliases are rewritten only as an exact whole-line `##` heading, never as
6
+ * prose. Partition slot coverage follows Scheme A: Case count may be smaller
7
+ * than the enum count, but every exact `TP-SP-...` slot must appear as an
8
+ * independent variant Test Point.
9
+ */
10
+ export const BACKEND_TEST_PLAN_HEADING_ALIASES = {
11
+ "模块索引": "Module Index",
12
+ "覆盖范围": "Coverage Scope",
13
+ "覆盖矩阵": "Coverage Matrix",
14
+ "场景分区": "Scenario Partitions",
15
+ };
16
+ export const BACKEND_TEST_SCENARIO_PARTITION_SLOTS_SCHEMA_ID = "backend-test-scenario-partition-slots-v1";
17
+ const CANONICAL_HEADING_LINE = /^##\s+(Module Index|Coverage Scope|Coverage Matrix|Scenario Partitions)\s*$/;
18
+ const ALIAS_HEADING_LINE = /^##\s+(模块索引|覆盖范围|覆盖矩阵|场景分区)\s*$/;
19
+ const CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-\d{2,3})\b/;
20
+ const SECTION_HEADING = /^###\s+/;
21
+ const TEST_POINT_RE = /\bTP-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g;
22
+ const VARIANT_LABEL = /变体测试点|Variant Test Points/i;
23
+ const FORBIDDEN_SLOT_ALIASES = new Set(["SINGLE", "MULTIPLE"]);
24
+ export function canonicalizeBackendTestPlanProtocolHeadings(markdown) {
25
+ const lines = markdown.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
26
+ const rewrittenHeadings = [];
27
+ const next = lines.map((line) => {
28
+ const trimmed = line.trim();
29
+ const alias = ALIAS_HEADING_LINE.exec(trimmed);
30
+ if (!alias)
31
+ return line;
32
+ const canonical = BACKEND_TEST_PLAN_HEADING_ALIASES[alias[1]];
33
+ rewrittenHeadings.push(canonical);
34
+ return `## ${canonical}`;
35
+ });
36
+ return {
37
+ markdown: next.join("\n"),
38
+ changed: rewrittenHeadings.length > 0,
39
+ rewrittenHeadings,
40
+ };
41
+ }
42
+ export function countCanonicalPlanHeading(markdown, heading) {
43
+ const canonical = canonicalizeBackendTestPlanProtocolHeadings(markdown).markdown;
44
+ return canonical
45
+ .split("\n")
46
+ .filter((line) => line.trim() === `## ${heading}`).length;
47
+ }
48
+ function orderedUnique(values) {
49
+ return [...new Set(values.filter(Boolean))];
50
+ }
51
+ function collectSectionTokens(body, headingNeedle) {
52
+ const lines = body.split("\n");
53
+ const start = lines.findIndex((line) => line.trim() === `### ${headingNeedle}`);
54
+ if (start < 0)
55
+ return [];
56
+ const tokens = [];
57
+ for (const line of lines.slice(start + 1)) {
58
+ if (SECTION_HEADING.test(line) || CASE_HEADING.test(line.trim()))
59
+ break;
60
+ tokens.push(...(line.match(TEST_POINT_RE) ?? []));
61
+ }
62
+ return tokens;
63
+ }
64
+ function collectLabeledTokens(body, label) {
65
+ return body
66
+ .split("\n")
67
+ .filter((line) => label.test(line))
68
+ .flatMap((line) => line.match(TEST_POINT_RE) ?? []);
69
+ }
70
+ export function collectMarkdownPartitionSlotCoverage(markdown, requiredVariantSlots) {
71
+ const declaredTestPoints = [];
72
+ const variantTestPoints = [];
73
+ const lines = markdown.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
74
+ const headingIndexes = lines.flatMap((line, index) => CASE_HEADING.test(line.trim()) ? [index] : []);
75
+ for (let i = 0; i < headingIndexes.length; i += 1) {
76
+ const start = headingIndexes[i];
77
+ const end = headingIndexes[i + 1] ?? lines.length;
78
+ const body = lines.slice(start, end).join("\n");
79
+ declaredTestPoints.push(...collectSectionTokens(body, "测试点"));
80
+ variantTestPoints.push(...collectLabeledTokens(body, VARIANT_LABEL));
81
+ }
82
+ const declaredSet = new Set(orderedUnique(declaredTestPoints));
83
+ const variantSet = new Set(orderedUnique(variantTestPoints));
84
+ const required = requiredVariantSlots.filter((slotId) => !FORBIDDEN_SLOT_ALIASES.has(slotId));
85
+ return {
86
+ declaredTestPoints: orderedUnique(declaredTestPoints),
87
+ variantTestPoints: orderedUnique(variantTestPoints),
88
+ missingFromDeclared: required.filter((slotId) => !declaredSet.has(slotId)),
89
+ missingFromVariant: required.filter((slotId) => !variantSet.has(slotId)),
90
+ };
91
+ }
92
+ export function isForbiddenPartitionSlotAlias(token) {
93
+ return FORBIDDEN_SLOT_ALIASES.has(token.trim().toUpperCase());
94
+ }
95
+ export function lookupRequiredVariantSlotsForMarkdownPath(inventory, markdownPath) {
96
+ if (!inventory)
97
+ return [];
98
+ const stem = markdownPath
99
+ .replace(/\\/g, "/")
100
+ .replace(/^.*\//, "")
101
+ .replace(/\.md$/i, "");
102
+ return inventory.modules.find((item) => item.stem === stem)?.requiredVariantSlots ?? [];
103
+ }
104
+ export function isCanonicalPlanHeadingLine(line) {
105
+ return CANONICAL_HEADING_LINE.test(line.trim());
106
+ }
@@ -1116,7 +1116,11 @@ function compareIntent(intent, observed, bound, example, field) {
1116
1116
  ? "UNDETERMINED"
1117
1117
  : "MISMATCH";
1118
1118
  }
1119
- return literal === expected
1119
+ const comparableObserved = observed.kind === "boolean"
1120
+ ? (observed.literal === "True" ? "true" : observed.literal === "False" ? "false" : literal)
1121
+ : literal;
1122
+ const comparableExpected = expected === "True" ? "true" : expected === "False" ? "false" : expected;
1123
+ return comparableObserved === comparableExpected
1120
1124
  ? "MATCH"
1121
1125
  : observed.kind === "unknown"
1122
1126
  ? "UNDETERMINED"
@@ -1207,6 +1211,12 @@ function boundedScenarioText(value, logicalLength) {
1207
1211
  const suffix = `<truncated length=${logicalLength ?? value.length}>`;
1208
1212
  return `${value.slice(0, Math.max(0, MAX_PERSISTED_SCENARIO_TEXT - suffix.length - 3))}...${suffix}`;
1209
1213
  }
1214
+ /** Facts schema forbids negative lengths. Internal helper sentinels stay in-memory only. */
1215
+ function persistedObservedLength(length) {
1216
+ return typeof length === "number" && Number.isFinite(length) && length >= 0
1217
+ ? Math.trunc(length)
1218
+ : undefined;
1219
+ }
1210
1220
  function localPythonFunctionRegion(source, functionName) {
1211
1221
  const escaped = functionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1212
1222
  const startMatch = new RegExp(`^def\\s+${escaped}\\s*\\(`, "m").exec(source);
@@ -1449,11 +1459,12 @@ function scenarioParamDiagnosticValues(intent, observed) {
1449
1459
  const expectedRaw = intent.startsWith("custom-literal:")
1450
1460
  ? intent.slice("custom-literal:".length)
1451
1461
  : intent === "unknown" ? undefined : intent;
1462
+ const persistedLength = persistedObservedLength(observed.length);
1452
1463
  return {
1453
1464
  expectedRaw,
1454
- observedRaw: boundedScenarioText(observed.text, observed.length),
1465
+ observedRaw: boundedScenarioText(observed.text, persistedLength),
1455
1466
  expectedNormalized: expectedRaw === undefined ? undefined : normalizeCustomLiteralExpected(expectedRaw).slice(0, 512),
1456
- observedNormalized: observed.literal === undefined ? undefined : boundedScenarioText(normalizeScenarioLiteralText(observed.literal), observed.length),
1467
+ observedNormalized: observed.literal === undefined ? undefined : boundedScenarioText(normalizeScenarioLiteralText(observed.literal), persistedLength),
1457
1468
  };
1458
1469
  }
1459
1470
  export async function assessBackendScenarioParamConsistency(input) {
@@ -1539,8 +1550,8 @@ export async function assessBackendScenarioParamConsistency(input) {
1539
1550
  tpId,
1540
1551
  field: inferred.field,
1541
1552
  intent: inferred.intent,
1542
- observed: boundedScenarioText(observed.text, observed.length),
1543
- observedLength: observed.length,
1553
+ observed: boundedScenarioText(observed.text, persistedObservedLength(observed.length)),
1554
+ observedLength: persistedObservedLength(observed.length),
1544
1555
  status,
1545
1556
  repairability,
1546
1557
  reasonCode: status === "MATCH" ? "SCENARIO_PARAM_MATCH" : status === "MISMATCH" ? "SCENARIO_PARAM_MISMATCH" : "SCENARIO_PARAM_UNDETERMINED",
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
  import { BACKEND_TEST_MAX_MODULE_COUNT, isOpaqueHashBackendTestModuleStem, isPriorityOnlyBackendTestModuleStem, } from "./backend-test-module-stem.js";
6
6
  import { resolveBackendTestLayout, } from "./backend-test-layout.js";
7
7
  import { canonicalizeBackendTestMarkdownTestPointBindings, inspectBackendTestMarkdownTestPointBindings, } from "./backend-test-case-coverage-analysis.js";
8
+ import { BACKEND_TEST_SCENARIO_PARTITION_SLOTS_SCHEMA_ID, canonicalizeBackendTestPlanProtocolHeadings, collectMarkdownPartitionSlotCoverage, lookupRequiredVariantSlotsForMarkdownPath, } from "./backend-test-plan-protocol.js";
8
9
  /**
9
10
  * Maps a backend-test generation writer task id to its writer-progress role.
10
11
  * Returns undefined for nodes that are not completeness-gated generators.
@@ -127,10 +128,8 @@ function looksLikeValidModuleStem(raw) {
127
128
  return moduleStemInvalidReason(raw) === undefined;
128
129
  }
129
130
  function extractCanonicalModuleIndexSection(readme) {
130
- const lines = readme
131
- .replaceAll("\r\n", "\n")
132
- .replaceAll("\r", "\n")
133
- .split("\n");
131
+ const canonical = canonicalizeBackendTestPlanProtocolHeadings(readme).markdown;
132
+ const lines = canonical.split("\n");
134
133
  const headings = lines.flatMap((line, index) => line.trim() === "## Module Index" ? [index] : []);
135
134
  if (headings.length === 0) {
136
135
  return { section: "", structureIssues: ["missing-module-index"] };
@@ -519,6 +518,19 @@ export async function loadBackendTestWriterProgressForRetry(runDir, taskId) {
519
518
  * child's own target file(s): md children are checked for structural
520
519
  * completeness, pytest children for parseability. Sibling modules are ignored.
521
520
  */
521
+ async function loadScenarioPartitionSlotInventory(runDir) {
522
+ if (!runDir)
523
+ return undefined;
524
+ try {
525
+ const raw = JSON.parse(await readFile(path.join(runDir, "contracts", "backend-test-scenario-partition-slots.json"), "utf8"));
526
+ if (raw?.schemaId !== BACKEND_TEST_SCENARIO_PARTITION_SLOTS_SCHEMA_ID)
527
+ return undefined;
528
+ return raw;
529
+ }
530
+ catch {
531
+ return undefined;
532
+ }
533
+ }
522
534
  export async function assessBackendTestShardChildCompleteness(input) {
523
535
  const role = input.role;
524
536
  const targetPaths = input.writeSet
@@ -531,6 +543,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
531
543
  let canonicalizationOutcome = role === "md-generate" ? "already-canonical" : undefined;
532
544
  let canonicalizedTestPointCount = 0;
533
545
  let lightweightRepairEligible = false;
546
+ const slotInventory = role === "md-generate"
547
+ ? await loadScenarioPartitionSlotInventory(input.runDir)
548
+ : undefined;
534
549
  for (const target of targetPaths) {
535
550
  // Rendered map-child writeSet entries are concrete (e.g.
536
551
  // testcase/md/health.md / testcase/test_orders.py); glob-only entries are
@@ -565,6 +580,27 @@ export async function assessBackendTestShardChildCompleteness(input) {
565
580
  .filter((entry) => entry.unclassifiedTestPoints.length > 0 || entry.duplicateBindingTestPoints.length > 0)
566
581
  .map((entry) => entry.caseId);
567
582
  const structural = moduleStructurallyComplete(body);
583
+ const requiredSlots = lookupRequiredVariantSlotsForMarkdownPath(slotInventory, target);
584
+ const slotCoverage = requiredSlots.length > 0
585
+ ? collectMarkdownPartitionSlotCoverage(body, requiredSlots)
586
+ : undefined;
587
+ const missingSlots = orderedUnique([
588
+ ...(slotCoverage?.missingFromDeclared ?? []),
589
+ ...(slotCoverage?.missingFromVariant ?? []),
590
+ ]);
591
+ if (missingSlots.length > 0) {
592
+ lightweightRepairEligible = true;
593
+ if (!brokenPaths.includes(target))
594
+ brokenPaths.push(target);
595
+ issues.push({
596
+ code: "T5",
597
+ path: target,
598
+ detail: `incomplete-partition-slot-set: missing ${missingSlots.join(", ")}; Scheme A requires each exact slot as an independent variant Test Point, not Case count or SINGLE/MULTIPLE aliases`,
599
+ recoverable: true,
600
+ affectedCases: afterBindings.map((entry) => entry.caseId),
601
+ testPointIds: missingSlots,
602
+ });
603
+ }
568
604
  if (!structural.ok) {
569
605
  lightweightRepairEligible = bindingCases.length > 0;
570
606
  if (bindingCases.length > 0)
@@ -651,7 +687,7 @@ export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layou
651
687
  }
652
688
  else {
653
689
  actualPaths.push(readmeRel);
654
- readme = await readFile(readmePath, "utf8");
690
+ readme = canonicalizeBackendTestPlanProtocolHeadings(await readFile(readmePath, "utf8")).markdown;
655
691
  if (!readme.includes("## Coverage Scope")) {
656
692
  brokenPaths.push(readmeRel);
657
693
  issues.push({
@@ -985,7 +1021,7 @@ export async function assessBackendTestMdPlanCompleteness(workspaceRoot, layout)
985
1021
  }
986
1022
  else {
987
1023
  actualPaths.push(planReadmeRel);
988
- const readme = await readFile(readmePath, "utf8");
1024
+ const readme = canonicalizeBackendTestPlanProtocolHeadings(await readFile(readmePath, "utf8")).markdown;
989
1025
  if (!readme.includes("## Coverage Scope")) {
990
1026
  brokenPaths.push(planReadmeRel);
991
1027
  issues.push({
@@ -4243,7 +4243,11 @@ const stripBackticks=s=>s.split(bt).join('');
4243
4243
  const invalidReason=raw=>{const st=norm(raw);if(/^p[0-2]$/.test(st))return 'priority-only-module-stem';if(/^[a-f][a-f0-9]{6,63}$/.test(st))return 'opaque-hash-module-stem';if(st==='readme')return 'reserved-module-stem';if(!/^[a-z][a-z0-9_]*$/.test(st))return 'invalid-syntax';if(/^(?:be|tp|ac|req|br)[_-]/i.test(st))return 'case-like-module-stem';return null;};
4244
4244
  const valid=raw=>invalidReason(raw)===null;
4245
4245
  const rxRelLink=/\\[[^\\]]+\\]\\(\\.\\/([A-Za-z0-9_.-]+)\\.md\\)/g;
4246
+ const headingAlias={'\u6a21\u5757\u7d22\u5f15':'Module Index','\u8986\u76d6\u8303\u56f4':'Coverage Scope','\u8986\u76d6\u77e9\u9635':'Coverage Matrix','\u573a\u666f\u5206\u533a':'Scenario Partitions'};
4246
4247
  const allLines=readme.replace(/\\r\\n/g,'\\n').replace(/\\r/g,'\\n').split('\\n');
4248
+ let headingRepair=false;
4249
+ for(let i=0;i<allLines.length;i++){const alias=/^##\\s+(模块索引|覆盖范围|覆盖矩阵|场景分区)\\s*$/.exec(allLines[i].trim());if(alias){allLines[i]='## '+headingAlias[alias[1]];headingRepair=true;}}
4250
+ if(headingRepair)readme=allLines.join('\\n');
4247
4251
  const headings=[];for(let i=0;i<allLines.length;i++){if(allLines[i].trim()==='## Module Index')headings.push(i);}
4248
4252
  if(headings.length!==1){process.stderr.write((headings.length===0?'missing-module-index':'duplicate-module-index')+'; require exactly one exact ## Module Index section\\n');process.exit(2);}
4249
4253
  const start=headings[0]+1;let end=allLines.length;for(let i=start;i<allLines.length;i++){if(/^##\\s+\\S/.test(allLines[i].trim())){end=i;break;}}
@@ -4256,7 +4260,7 @@ const headerIndex=lines.findIndex(line=>{const row=cells(line);return expectedHe
4256
4260
  if(headerIndex<0){process.stderr.write('invalid-module-index-header: require exact business ownership and path columns\\n');process.exit(2);}
4257
4261
  const dataRows=lines.slice(headerIndex+2).map(cells).filter(row=>row.length>=8&&row[0]&&row[0]!=='Module Stem');
4258
4262
  const allowedSplit=new Set(['explicit-user-layout','primary-business-resource','independent-business-resource','output-budget']);
4259
- const operationOwners=new Map();const canonicalSeen=new Set();const declaredModules=[];let planRepairApplied=false;
4263
+ const operationOwners=new Map();const canonicalSeen=new Set();const declaredModules=[];let planRepairApplied=headingRepair;
4260
4264
  for(const row of dataRows){
4261
4265
  const rawStem=String(row[0]||'').trim(),resource=String(row[1]||'').trim(),operations=String(row[2]||'').split(';').map(value=>value.trim()).filter(Boolean),split=String(row[5]||'').trim();
4262
4266
  const mdMatches=[...String(row[6]||'').matchAll(rxMdPath)];
@@ -4298,6 +4302,8 @@ if(planRepairApplied){
4298
4302
  const table=['| '+expectedHeader.join(' | ')+' |','|'+expectedHeader.map(()=>'---').join('|')+'|',...declaredModules.map(item=>'| '+[item.stem,item.businessResource,item.ownedOperations.join('; '),item.ownedRuleKeys.join('; '),item.caseIds.join('; '),item.splitReason,'['+item.stem+'](./'+item.stem+'.md) '+item.markdownPath,item.pytestPath].join(' | ')+' |')].join('\\n');
4299
4303
  readme=[...allLines.slice(0,headings[0]+1),table,...allLines.slice(end)].join('\\n');
4300
4304
  fs.writeFileSync(planPath,readme,'utf8');
4305
+ } else if(headingRepair){
4306
+ fs.writeFileSync(planPath,readme,'utf8');
4301
4307
  }
4302
4308
  for(const [operation,owners] of operationOwners){if(owners.length>1&&!owners.every(owner=>owner.split==='explicit-user-layout'||owner.split==='output-budget')){process.stderr.write('overlapping-operation-modules: '+operation+' -> '+owners.map(owner=>owner.stem).join(',')+'; merge by business resource or use an authoritative explicit-user-layout\\n');process.exit(2);}}
4303
4309
  if(!strictLayout){
@@ -4315,7 +4321,34 @@ if(modules.length===0){process.stderr.write('empty-module-index: require at leas
4315
4321
  if(modules.length>8){process.stderr.write('excessive-module-count: '+modules.length+' > 8; merge by the smallest stable business resource/domain set\\n');process.exit(2);}
4316
4322
  const planReadPath=path.relative(process.cwd(),planPath).split(path.sep).join('/');
4317
4323
  const planSha256=crypto.createHash('sha256').update(readme).digest('hex');
4318
- process.stdout.write(JSON.stringify({modules:modules.map(item=>({...item,planReadPath})),planReadPath,planSha256,moduleLayout:{schemaId:'backend-test-module-layout-facts-v1',source:strictLayout?'task-contract':'plan-derived',contractSha256:strictLayoutSha256,mode:strictLayout&&strictLayout.mode||'business-resource-layout',planRepairAttemptCount:planRepairApplied?1:0,planRepairOutcome:planRepairApplied?'changed':'not-required'}}));
4324
+ const slotTok=v=>String(v).trim().toUpperCase().replace(/[^A-Z0-9]+/g,'-').replace(/^-+|-+$/g,'');
4325
+ const partHead=[];for(let i=0;i<allLines.length;i++){if(allLines[i].trim()==='## Scenario Partitions')partHead.push(i);}
4326
+ if(partHead.length>1){process.stderr.write('duplicate-scenario-partitions; require at most one exact ## Scenario Partitions section\\n');process.exit(2);}
4327
+ const slotByModule=new Map(modules.map(m=>[m.stem,[]]));const unassigned=[];
4328
+ if(partHead.length===1){
4329
+ const pStart=partHead[0]+1;let pEnd=allLines.length;for(let i=pStart;i<allLines.length;i++){if(/^##\\s+\\S/.test(allLines[i].trim())){pEnd=i;break;}}
4330
+ const pLines=allLines.slice(pStart,pEnd).filter(l=>l.includes('|'));
4331
+ const pCells=line=>line.split('|').slice(1,-1).map(v=>stripBackticks(v).trim());
4332
+ const pHeader=['Partition ID','Operation','Axis','Domain','Required Slots','Expected by Slot','Bind Rule'];
4333
+ const pH=pLines.findIndex(line=>pHeader.every((v,i)=>pCells(line)[i]===v));
4334
+ if(pH<0){process.stderr.write('MISSING_PARTITION_TABLE: Scenario Partitions section has no canonical header row\\n');process.exit(2);}
4335
+ const pRows=pLines.slice(pH+2).map(pCells).filter(r=>r.length>=7&&r[0]&&r[0]!=='Partition ID');
4336
+ for(const row of pRows){
4337
+ const pid=String(row[0]||'').trim(),op=String(row[1]||'').trim(),domain=String(row[3]||'').split(/[;,,;]/).map(v=>v.trim()).filter(Boolean),optional=/omit/i.test(String(row[4]||'')),bind=String(row[6]||'').trim();
4338
+ const prefix=/^SP-[A-Z0-9][A-Z0-9._-]*$/i.test(pid)?('TP-'+pid.toUpperCase()):('TP-SP-'+slotTok(op)+'-'+slotTok(String(row[2]||'')));
4339
+ const slotIds=[...domain.map(v=>prefix+'-'+slotTok(v)),...(optional?[prefix+'-OMITTED']:[]),prefix+'-NOT-IN-SET'];
4340
+ const owners=modules.filter(m=>(m.ownedRuleKeys||[]).includes(bind)||(m.ownedOperations||[]).some(o=>String(o).replace(/\\s+/g,' ').toUpperCase()===op.replace(/\\s+/g,' ').toUpperCase()));
4341
+ const uniqueOwners=[...new Set(owners.map(o=>o.stem))];
4342
+ if(uniqueOwners.length===1){for(const id of slotIds)slotByModule.get(uniqueOwners[0]).push(id);}
4343
+ else if(modules.length===1){for(const id of slotIds)slotByModule.get(modules[0].stem).push(id);}
4344
+ else unassigned.push(...slotIds);
4345
+ }
4346
+ }
4347
+ if(unassigned.length){process.stderr.write('unassigned-partition-slots: '+unassigned.join(',')+'\\n');process.exit(2);}
4348
+ const slotInventory={schemaId:'backend-test-scenario-partition-slots-v1',planSha256,modules:modules.map(m=>({stem:m.stem,requiredVariantSlots:[...new Set(slotByModule.get(m.stem)||[])]})),unassignedSlotIds:[]};
4349
+ fs.mkdirSync(path.join(runDir,'contracts'),{recursive:true});
4350
+ fs.writeFileSync(path.join(runDir,'contracts','backend-test-scenario-partition-slots.json'),JSON.stringify(slotInventory,null,2));
4351
+ process.stdout.write(JSON.stringify({modules:modules.map(item=>({...item,planReadPath,requiredVariantSlots:(slotInventory.modules.find(m=>m.stem===item.stem)||{requiredVariantSlots:[]}).requiredVariantSlots})),planReadPath,planSha256,moduleLayout:{schemaId:'backend-test-module-layout-facts-v1',source:strictLayout?'task-contract':'plan-derived',contractSha256:strictLayoutSha256,mode:strictLayout&&strictLayout.mode||'business-resource-layout',planRepairAttemptCount:planRepairApplied?1:0,planRepairOutcome:planRepairApplied?'changed':'not-required'},partitionSlots:slotInventory}));
4319
4352
  `;
4320
4353
  const encoded = deflateRawSync(Buffer.from(script, "utf8")).toString("base64");
4321
4354
  return `node -e "eval(require('zlib').inflateRawSync(Buffer.from('${encoded}','base64')).toString('utf8'))"`;
@@ -4896,9 +4929,9 @@ async function buildBackendTestHybridDag(sources) {
4896
4929
  subtask_prompt: [
4897
4930
  "This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write testcase/md/README.md or any project file; module case cards are written by downstream sharded nodes.",
4898
4931
  "Output budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.",
4899
- "Return the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact ## Coverage Scope, ## Coverage Matrix and ## Module Index sections required by the downstream manifest.",
4932
+ "Return the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact English protocol headings ## Coverage Scope, ## Coverage Matrix, ## Scenario Partitions when applicable, and ## Module Index. Never translate those headings into 覆盖范围/覆盖矩阵/场景分区/模块索引.",
4900
4933
  "Read the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under testcase/md/.",
4901
- "Write human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.",
4934
+ "Write human-readable content in Simplified Chinese by default. Keep English protocol literals exact: section headings, table headers, Partition IDs, TP IDs, Case IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and source citations. Never translate ## Module Index into ## 模块索引.",
4902
4935
  "Create the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.",
4903
4936
  "Before the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.",
4904
4937
  "Coverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.",
@@ -4914,7 +4947,7 @@ async function buildBackendTestHybridDag(sources) {
4914
4947
  ]
4915
4948
  : []),
4916
4949
  "Include exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. Group by stable business resource/domain, not by CRUD operation, AC, parameter/field axis, scenario type or regression purpose: one resource's list/detail/create/update/delete and its filters/response assertions/regression floor belong in one module. Multiple modules owning the same exact `METHOD /path` are forbidden unless every such row is `explicit-user-layout` from primary-requirement path pairs or has a documented `output-budget` proof. Keep the total module count at the smallest safe value and never exceed 8 modules. Name model-derived modules with stable lowercase business stems such as `health` or `resource_notes`; explicit-user-layout preserves the primary requirement filename stem even when it is more specific. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix. Pure hexadecimal/hash-like opaque stems and test-purpose-only stems are forbidden. Do not use Case-ID-like module filenames. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/test_<module>.py`, while explicit-user-layout preserves the primary requirement paths. Do not hand-write a conflicting module count in prose; the Module Index row count is the only count truth.",
4917
- "Scenario Partitions (query/filter axes): for every affected GET/list operation, declare one row per enum or classification axis used for filtering (query/path parameters such as type/status/category). Add a mandatory machine-readable `## Scenario Partitions` section after the Coverage Matrix using exactly `| Partition ID | Operation | Axis | Domain | Required Slots | Expected by Slot | Bind Rule |` with the separator row. Partition ID is a stable `SP-<OPERATION>-<AXIS>` token; Domain must copy the legal values verbatim from the bound OpenAPI enum or requirement sentence (never guess), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. Expected by Slot states the documented expectation per slot kind (`domain-value`, `default-behavior`, `empty-result`/`excluded-result` when documented, or `GAP` when the source does not document the complement expectation — never invent 空列表/400). POST/PUT body field-validation enums stay in the Coverage Matrix as `TP-<FIELD>-ENUM-*` and MUST NOT get a Scenario Partition row. Do not create partitions for axes without a documented legal-value domain. Only GET/list query or path parameters whose bound source documents a finite enum or classification set may become a Scenario Partition. Do not create partitions for free-form strings, primary keys, required-or-optional-only parameters, or boundary/format-only axes. If an axis has no finite legal-value domain, do not declare a Partition row and do not invent NOT-IN-SET cases. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.",
4950
+ "Scenario Partitions (query/filter axes): for every affected GET/list operation, declare one row per enum or classification axis used for filtering (query/path parameters such as type/status/category). Add a mandatory machine-readable `## Scenario Partitions` section after the Coverage Matrix using exactly `| Partition ID | Operation | Axis | Domain | Required Slots | Expected by Slot | Bind Rule |` with the separator row. Partition ID is a stable `SP-<OPERATION>-<AXIS>` token; Domain must copy the legal values verbatim from the bound OpenAPI enum or requirement sentence (never guess), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. Scheme A: Case count may be smaller than the enum count, but every exact slot still needs an independent variant Test Point and pytest.param id; never use SINGLE/MULTIPLE aliases as coverage. Expected by Slot states the documented expectation per slot kind (`domain-value`, `default-behavior`, `empty-result`/`excluded-result` when documented, or `GAP` when the source does not document the complement expectation — never invent 空列表/400). POST/PUT body field-validation enums stay in the Coverage Matrix as `TP-<FIELD>-ENUM-*` and MUST NOT get a Scenario Partition row. Do not create partitions for axes without a documented legal-value domain. Only GET/list query or path parameters whose bound source documents a finite enum or classification set may become a Scenario Partition. Do not create partitions for free-form strings, primary keys, required-or-optional-only parameters, or boundary/format-only axes. If an axis has no finite legal-value domain, do not declare a Partition row and do not invent NOT-IN-SET cases. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.",
4918
4951
  "Before finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
4919
4952
  ...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
4920
4953
  intake.boundedSourceContext,
@@ -4998,7 +5031,7 @@ async function buildBackendTestHybridDag(sources) {
4998
5031
  'Write the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under "## 测试类 ..." (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case\'s `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.',
4999
5032
  "Name this module file with the exact frozen Module Index stem `{{item.stem}}` (filename `{{item.markdownPath}}`). Never reinterpret or rename an explicit-user-layout stem. Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Pure hexadecimal/hash-like opaque stems such as `a401606` and `deadbeef` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `{{item.pytestPath}}`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
5000
5033
  "AUTOMATION_BINDING_FORMAT_V1 is a literal machine contract. Under every Case's `### 自动化映射`, write these independent lines exactly: `- 脚本:<path|无>`, `- primary symbol:<symbol|无>`, `- 变体测试点:<semicolon-separated TP IDs|无>`, `- 场景断言测试点:<semicolon-separated TP IDs|无>`, `- 横切证据测试点:<semicolon-separated TP IDs|无>`. TP IDs must be on the same line after the colon. Forbidden classification forms include `TP-X(变体测试点)`, `[变体测试点] TP-X`, `【变体测试点】:TP-X`, pipe-delimited annotations, tables, or nested TP lists. Before returning, verify that the Case `### 测试点` exact set equals the pairwise-disjoint union of the three canonical binding lines; do not add, remove, rename or duplicate a TP to make the format pass.",
5001
- "Scenario Partition slots: when README declares `## Scenario Partitions`, every slot of each declared partition MUST appear in this module's Cases as exactly one variant Test Point with the deterministic ID `TP-<Partition ID>-<VALUE-TOKEN>` (each-value), `TP-<Partition ID>-OMITTED` (optional axis only) and exactly one `TP-<Partition ID>-NOT-IN-SET` complement slot with `intent=enum-invalid`. Example: Partition ID `SP-GET-API-RESOURCE-NOTES-STATUS` → `TP-SP-GET-API-RESOURCE-NOTES-STATUS-ACTIVE`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Before returning, derive the complete exact slot set from every applicable Scenario Partitions row and verify that the module Cases declare and bind every slot assigned by the Coverage Matrix; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.",
5034
+ "Scenario Partition slots: requiredVariantSlots for this module are `{{item.requiredVariantSlots}}`. Every listed ID MUST appear in this module's Cases as exactly one variant Test Point in both `### 测试点` and `变体测试点`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: one Case may carry many slots; do not create one Case per enum value just to match Case count. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.",
5002
5035
  "For every variant Test Point, write its machine-checkable `场景意图: <TP-ID>; operation=...; target=...; intent=...` line inside that same Case body/自动化映射. Never collect Scenario Intent lines in a file-level appendix, implementation-details block, or another Case; local TP ownership is mandatory.",
5003
5036
  "Every Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.",
5004
5037
  "In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Each Case describes exactly one target request payload contract: put every payload label on its own list line, never concatenate multiple operations or setup POST/PUT contracts into one label line, and never repeat a `Payload Contract:` token inside explanatory prose/details after the machine-readable line. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. If the bound contract only says an identifier is returned/present, do not declare a concrete identifier type. If a 404 Case needs a nonexistent path identifier but its syntax/type is unspecified, define a create-delete-derived valid identifier journey instead of an arbitrary UUID/text placeholder. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
@@ -5043,7 +5076,7 @@ async function buildBackendTestHybridDag(sources) {
5043
5076
  "Correct testcase/md/** directly: add documented omissions, remove unsupported cases, preserve every frozen Module Index filename exactly (never rename an explicit-user-layout module; model-derived invalid stems must have been rejected before map expansion), normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.",
5044
5077
  "This is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.",
5045
5078
  "For affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.",
5046
- "Scenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
5079
+ "Scenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: keep existing Case structure and add missing exact variant Test Points to already-assigned Cases instead of creating one Case per enum value. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
5047
5080
  "Before returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in the run-owned plan or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.",
5048
5081
  "Read only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
5049
5082
  ...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
@@ -5153,7 +5186,7 @@ async function buildBackendTestHybridDag(sources) {
5153
5186
  "Output budget protocol (hard, max output <=16K per turn): Write exactly the frozen `{{item.pytestPath}}`. Never paste full Python modules into assistant chat. Do not merge or split modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.",
5154
5187
  "Align every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them. Hard contract: intent=enum-invalid MUST pass a concrete invalid value literal (string/number/boolean), never `_OMIT`/None/missing key; intent=missing/empty may use `_OMIT` or delete the key; intent=custom-literal:trim|whitespace-padded requires a leading/trailing whitespace string with non-empty trimmed content (all-whitespace belongs to empty/whitespace-only, not trim); intent=custom-literal:ACTIVE|ARCHIVED requires the exact enum string, never descriptive tokens like filter-active; intent=max/min/max+1 should pass a repeated-string length expression, a bare length number N, or a helper named _*_LEN{N} / _*_MAX_LENGTH / _*_OVER_LENGTH — never a bare 1 for oversize. Hard contract: request payload dicts may only contain DTO field keys from Payload Allowed Paths; never put expect/expected/echo_* helper keys inside the JSON body dict. Path/query/header identifiers and scenario-control metadata (including `id`, expected codes, and selector labels) must stay in separate pytest parameters and helper arguments; never merge them into a DTO patch or JSON body unless that exact path is allowed by the Markdown payload contract. Normalize the configured API base URL with `rstrip(\"/\")` (or equivalently join exactly one slash) before appending endpoint paths; generated requests must never contain a `//api/...` path. When the bound source documents a concrete non-secret local API URL, generated clients must use it as the fallback in `os.environ.get(\"API_BASE_URL\", \"<documented-url>\")`; do not require an otherwise-uninjected environment variable or fail setup solely because it is absent. Missing-field helpers must remove keys idempotently with `payload.pop(field, None)`, never `del payload[field]`, because optional fields may already be absent.",
5155
5188
  "For every response contract that requires an object or pagination envelope, first assert that each envelope/data value is a dict and that required keys exist, then index fields and assert values. Never let an incidental KeyError or list/string TypeError stand in for the explicit response-shape contract failure.",
5156
- 'Ensure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. Use a literal direct `pytest.param(..., id=...)` expression for every row; never hide or wrap it behind `_post_case`, `_put_case`, row-factory functions, comprehensions, generators, or dynamically returned parameter lists; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). Implement request dictionaries so their direct and nested key paths and enum literals exactly satisfy the Case `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; for `Payload Contract: none`, do not invent a JSON/body DTO. GET/DELETE setup journeys may create resources, but their setup DTO must not change the target operation\'s no-body payload contract. No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task\'s explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.',
5189
+ 'Ensure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. A Case with exactly one variant Test Point still needs one literal `pytest.param(..., id="TP-...")` row; never leave a single-variant Case as a bare function with the TP only in the docstring. Use a literal direct `pytest.param(..., id=...)` expression for every row; never hide or wrap it behind `_post_case`, `_put_case`, row-factory functions, comprehensions, generators, or dynamically returned parameter lists; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). Implement request dictionaries so their direct and nested key paths and enum literals exactly satisfy the Case `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; for `Payload Contract: none`, do not invent a JSON/body DTO. GET/list filters still declare query fields in those payload labels when the Case varies `params=`/`query=` keys. Python `True`/`False` may implement JSON/OpenAPI `true`/`false` query or body booleans. GET/DELETE setup journeys may create resources, but their setup DTO must not change the target operation\'s no-body payload contract. No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task\'s explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.',
5157
5190
  "Name the generated pytest file so it corresponds one-to-one with its source Markdown module file: this module stem `{{item.stem}}` maps to exactly the frozen `{{item.pytestPath}}`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `resource_notes` → `testcase/test_resource_notes.py`, `health` → `testcase/test_health.py`. If Markdown automation mapping names a different path than this module stem path, still write the frozen manifest pytest path and do not invent prefixes. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
5158
5191
  "Scenario Partition slots: every `TP-<Partition ID>-...` variant Test Point declared by this module's Markdown MUST become exactly one literal direct `pytest.param(..., id=\"TP-<Partition ID>-...\")` row with the exact slot ID; the not-in-set slot passes a concrete literal absent from the documented Domain (e.g. `UNKNOWN_TYPE`) — never `_OMIT`, never a descriptive token. Never split one slot into multiple params or merge several slots under a family TP id. Slot filtering requests hit the documented list endpoint with the slot value as the query/path filter.",
5159
5192
  "Keep this module self-contained: define module-local fixtures and helpers directly in `{{item.pytestPath}}`, so pytest discovers every fixture dependency without external plugin registration. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. Recursively redact sensitive values and apply bounded truncation before logging.",
@@ -19,6 +19,40 @@ const RERUN_FEEDBACK_ROLES = new Set([
19
19
  "verifier",
20
20
  "reviewer",
21
21
  ]);
22
+ function globPatternToRegExp(pattern) {
23
+ const normalized = pattern.replace(/\\/g, "/");
24
+ let source = "";
25
+ for (let index = 0; index < normalized.length; index += 1) {
26
+ const char = normalized[index];
27
+ if (char === "*" && normalized[index + 1] === "*") {
28
+ source += ".*";
29
+ index += 1;
30
+ }
31
+ else if (char === "*") {
32
+ source += "[^/]*";
33
+ }
34
+ else {
35
+ source += char.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
36
+ }
37
+ }
38
+ return new RegExp(`^${source}$`);
39
+ }
40
+ function readSetAllowsArtifactPath(task, artifactPath) {
41
+ const readSet = task.readSet ?? [];
42
+ if (readSet.length === 0)
43
+ return true;
44
+ const candidate = artifactPath.replace(/\\/g, "/");
45
+ const candidates = [candidate];
46
+ for (const marker of ["/.harness/", "/source/", "/testcase/"]) {
47
+ const index = candidate.indexOf(marker);
48
+ if (index >= 0)
49
+ candidates.push(candidate.slice(index + 1));
50
+ }
51
+ return readSet.some((pattern) => {
52
+ const regex = globPatternToRegExp(pattern);
53
+ return candidates.some((value) => regex.test(value));
54
+ });
55
+ }
22
56
  function formatBulletList(items, emptyLabel) {
23
57
  if (!items || items.length === 0)
24
58
  return emptyLabel;
@@ -129,7 +163,7 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
129
163
  : artifactKind === "assistant"
130
164
  ? record.assistantArtifactPath
131
165
  : undefined;
132
- if (truncated && artifactPath) {
166
+ if (truncated && artifactPath && readSetAllowsArtifactPath(task, artifactPath)) {
133
167
  section += formatUpstreamArtifactPointerMap(record, artifactKind === "stdout" ? "stdout" : "assistant");
134
168
  }
135
169
  else if (truncated && record.nodeRecordPath) {
@@ -205,7 +239,7 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
205
239
  if (consumedArtifactSection) {
206
240
  section += `\n\n${consumedArtifactSection}`;
207
241
  }
208
- if (truncated && artifactPath) {
242
+ if (truncated && artifactPath && readSetAllowsArtifactPath(task, artifactPath)) {
209
243
  section += formatUpstreamArtifactPointerMap(record, artifactKind);
210
244
  }
211
245
  sections.push(section);
@@ -148,7 +148,7 @@
148
148
  "validate-backend-test-environment-shell"
149
149
  ],
150
150
  "complexity": "MED",
151
- "subtask_prompt": "This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write testcase/md/README.md or any project file; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nReturn the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact ## Coverage Scope, ## Coverage Matrix and ## Module Index sections required by the downstream manifest.\n\nRead the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under testcase/md/.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nCreate the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside the plan artifact itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module layout contract: first inspect the PRIMARY requirement for an explicit list of required Markdown/Python output path pairs. When explicit paths are present, they are authoritative `explicit-user-layout`: reproduce their exact filenames, count and one-to-one pairs in Module Index; do not rename, merge, split, omit or add a module from reference/example scripts. Only when the primary requirement has no explicit file layout may you derive the smallest `business-resource-layout`. Existing examples, historical regression functions and shared setup may add evidence/assertions to an existing required module, but never create an extra physical module by themselves. A reference-only `resp_regression`, positive/negative/boundary/error/response module is forbidden. The Module Stem cell must contain only the plain filename stem; never put Markdown link syntax or a path in that cell.\n\nInclude exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. Group by stable business resource/domain, not by CRUD operation, AC, parameter/field axis, scenario type or regression purpose: one resource's list/detail/create/update/delete and its filters/response assertions/regression floor belong in one module. Multiple modules owning the same exact `METHOD /path` are forbidden unless every such row is `explicit-user-layout` from primary-requirement path pairs or has a documented `output-budget` proof. Keep the total module count at the smallest safe value and never exceed 8 modules. Name model-derived modules with stable lowercase business stems such as `health` or `resource_notes`; explicit-user-layout preserves the primary requirement filename stem even when it is more specific. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix. Pure hexadecimal/hash-like opaque stems and test-purpose-only stems are forbidden. Do not use Case-ID-like module filenames. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/test_<module>.py`, while explicit-user-layout preserves the primary requirement paths. Do not hand-write a conflicting module count in prose; the Module Index row count is the only count truth.\n\nScenario Partitions (query/filter axes): for every affected GET/list operation, declare one row per enum or classification axis used for filtering (query/path parameters such as type/status/category). Add a mandatory machine-readable `## Scenario Partitions` section after the Coverage Matrix using exactly `| Partition ID | Operation | Axis | Domain | Required Slots | Expected by Slot | Bind Rule |` with the separator row. Partition ID is a stable `SP-<OPERATION>-<AXIS>` token; Domain must copy the legal values verbatim from the bound OpenAPI enum or requirement sentence (never guess), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. Expected by Slot states the documented expectation per slot kind (`domain-value`, `default-behavior`, `empty-result`/`excluded-result` when documented, or `GAP` when the source does not document the complement expectation — never invent 空列表/400). POST/PUT body field-validation enums stay in the Coverage Matrix as `TP-<FIELD>-ENUM-*` and MUST NOT get a Scenario Partition row. Do not create partitions for axes without a documented legal-value domain. Only GET/list query or path parameters whose bound source documents a finite enum or classification set may become a Scenario Partition. Do not create partitions for free-form strings, primary keys, required-or-optional-only parameters, or boundary/format-only axes. If an axis has no finite legal-value domain, do not declare a Partition row and do not invent NOT-IN-SET cases. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.\n\nBefore finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
151
+ "subtask_prompt": "This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write testcase/md/README.md or any project file; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nReturn the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact English protocol headings ## Coverage Scope, ## Coverage Matrix, ## Scenario Partitions when applicable, and ## Module Index. Never translate those headings into 覆盖范围/覆盖矩阵/场景分区/模块索引.\n\nRead the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under testcase/md/.\n\nWrite human-readable content in Simplified Chinese by default. Keep English protocol literals exact: section headings, table headers, Partition IDs, TP IDs, Case IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and source citations. Never translate ## Module Index into ## 模块索引.\n\nCreate the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside the plan artifact itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module layout contract: first inspect the PRIMARY requirement for an explicit list of required Markdown/Python output path pairs. When explicit paths are present, they are authoritative `explicit-user-layout`: reproduce their exact filenames, count and one-to-one pairs in Module Index; do not rename, merge, split, omit or add a module from reference/example scripts. Only when the primary requirement has no explicit file layout may you derive the smallest `business-resource-layout`. Existing examples, historical regression functions and shared setup may add evidence/assertions to an existing required module, but never create an extra physical module by themselves. A reference-only `resp_regression`, positive/negative/boundary/error/response module is forbidden. The Module Stem cell must contain only the plain filename stem; never put Markdown link syntax or a path in that cell.\n\nInclude exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. Group by stable business resource/domain, not by CRUD operation, AC, parameter/field axis, scenario type or regression purpose: one resource's list/detail/create/update/delete and its filters/response assertions/regression floor belong in one module. Multiple modules owning the same exact `METHOD /path` are forbidden unless every such row is `explicit-user-layout` from primary-requirement path pairs or has a documented `output-budget` proof. Keep the total module count at the smallest safe value and never exceed 8 modules. Name model-derived modules with stable lowercase business stems such as `health` or `resource_notes`; explicit-user-layout preserves the primary requirement filename stem even when it is more specific. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix. Pure hexadecimal/hash-like opaque stems and test-purpose-only stems are forbidden. Do not use Case-ID-like module filenames. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/test_<module>.py`, while explicit-user-layout preserves the primary requirement paths. Do not hand-write a conflicting module count in prose; the Module Index row count is the only count truth.\n\nScenario Partitions (query/filter axes): for every affected GET/list operation, declare one row per enum or classification axis used for filtering (query/path parameters such as type/status/category). Add a mandatory machine-readable `## Scenario Partitions` section after the Coverage Matrix using exactly `| Partition ID | Operation | Axis | Domain | Required Slots | Expected by Slot | Bind Rule |` with the separator row. Partition ID is a stable `SP-<OPERATION>-<AXIS>` token; Domain must copy the legal values verbatim from the bound OpenAPI enum or requirement sentence (never guess), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. Scheme A: Case count may be smaller than the enum count, but every exact slot still needs an independent variant Test Point and pytest.param id; never use SINGLE/MULTIPLE aliases as coverage. Expected by Slot states the documented expectation per slot kind (`domain-value`, `default-behavior`, `empty-result`/`excluded-result` when documented, or `GAP` when the source does not document the complement expectation — never invent 空列表/400). POST/PUT body field-validation enums stay in the Coverage Matrix as `TP-<FIELD>-ENUM-*` and MUST NOT get a Scenario Partition row. Do not create partitions for axes without a documented legal-value domain. Only GET/list query or path parameters whose bound source documents a finite enum or classification set may become a Scenario Partition. Do not create partitions for free-form strings, primary keys, required-or-optional-only parameters, or boundary/format-only axes. If an axis has no finite legal-value domain, do not declare a Partition row and do not invent NOT-IN-SET cases. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.\n\nBefore finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
152
152
  "executor": "pi",
153
153
  "role": "planner",
154
154
  "toolProfile": "read-only",
@@ -193,7 +193,7 @@
193
193
  "writePolicy": "read-only",
194
194
  "shell": {
195
195
  "commands": [
196
- "node -e \"eval(require('zlib').inflateRawSync(Buffer.from('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','base64')).toString('utf8'))\""
196
+ "node -e \"eval(require('zlib').inflateRawSync(Buffer.from('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','base64')).toString('utf8'))\""
197
197
  ],
198
198
  "timeoutMs": 60000,
199
199
  "cwd": "."
@@ -245,7 +245,7 @@
245
245
  "verification-before-completion"
246
246
  ],
247
247
  "complexity": "MED",
248
- "subtaskPromptTemplate": "This is a required file-generation node for exactly one Markdown module. Read the upstream run-owned Markdown plan artifact at `{{item.planReadPath}}` (Coverage Scope + Coverage Matrix + Module Index) and the bounded references, then immediately use write tools to create the single frozen file `{{item.markdownPath}}`. Do not read or recreate testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify any other module file.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, other modules' case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file (this module). Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the module file has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nWrite the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under \"## 测试类 ...\" (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case's `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.\n\nName this module file with the exact frozen Module Index stem `{{item.stem}}` (filename `{{item.markdownPath}}`). Never reinterpret or rename an explicit-user-layout stem. Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Pure hexadecimal/hash-like opaque stems such as `a401606` and `deadbeef` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `{{item.pytestPath}}`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nAUTOMATION_BINDING_FORMAT_V1 is a literal machine contract. Under every Case's `### 自动化映射`, write these independent lines exactly: `- 脚本:<path|无>`, `- primary symbol:<symbol|无>`, `- 变体测试点:<semicolon-separated TP IDs|无>`, `- 场景断言测试点:<semicolon-separated TP IDs|无>`, `- 横切证据测试点:<semicolon-separated TP IDs|无>`. TP IDs must be on the same line after the colon. Forbidden classification forms include `TP-X(变体测试点)`, `[变体测试点] TP-X`, `【变体测试点】:TP-X`, pipe-delimited annotations, tables, or nested TP lists. Before returning, verify that the Case `### 测试点` exact set equals the pairwise-disjoint union of the three canonical binding lines; do not add, remove, rename or duplicate a TP to make the format pass.\n\nScenario Partition slots: when README declares `## Scenario Partitions`, every slot of each declared partition MUST appear in this module's Cases as exactly one variant Test Point with the deterministic ID `TP-<Partition ID>-<VALUE-TOKEN>` (each-value), `TP-<Partition ID>-OMITTED` (optional axis only) and exactly one `TP-<Partition ID>-NOT-IN-SET` complement slot with `intent=enum-invalid`. Example: Partition ID `SP-GET-API-RESOURCE-NOTES-STATUS` → `TP-SP-GET-API-RESOURCE-NOTES-STATUS-ACTIVE`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Before returning, derive the complete exact slot set from every applicable Scenario Partitions row and verify that the module Cases declare and bind every slot assigned by the Coverage Matrix; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.\n\nFor every variant Test Point, write its machine-checkable `场景意图: <TP-ID>; operation=...; target=...; intent=...` line inside that same Case body/自动化映射. Never collect Scenario Intent lines in a file-level appendix, implementation-details block, or another Case; local TP ownership is mandatory.\n\nEvery Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.\n\nIn every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Each Case describes exactly one target request payload contract: put every payload label on its own list line, never concatenate multiple operations or setup POST/PUT contracts into one label line, and never repeat a `Payload Contract:` token inside explanatory prose/details after the machine-readable line. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. If the bound contract only says an identifier is returned/present, do not declare a concrete identifier type. If a 404 Case needs a nonexistent path identifier but its syntax/type is unspecified, define a create-delete-derived valid identifier journey instead of an arbitrary UUID/text placeholder. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
248
+ "subtaskPromptTemplate": "This is a required file-generation node for exactly one Markdown module. Read the upstream run-owned Markdown plan artifact at `{{item.planReadPath}}` (Coverage Scope + Coverage Matrix + Module Index) and the bounded references, then immediately use write tools to create the single frozen file `{{item.markdownPath}}`. Do not read or recreate testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify any other module file.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, other modules' case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file (this module). Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the module file has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nWrite the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under \"## 测试类 ...\" (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case's `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.\n\nName this module file with the exact frozen Module Index stem `{{item.stem}}` (filename `{{item.markdownPath}}`). Never reinterpret or rename an explicit-user-layout stem. Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Pure hexadecimal/hash-like opaque stems such as `a401606` and `deadbeef` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `{{item.pytestPath}}`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\n\nAUTOMATION_BINDING_FORMAT_V1 is a literal machine contract. Under every Case's `### 自动化映射`, write these independent lines exactly: `- 脚本:<path|无>`, `- primary symbol:<symbol|无>`, `- 变体测试点:<semicolon-separated TP IDs|无>`, `- 场景断言测试点:<semicolon-separated TP IDs|无>`, `- 横切证据测试点:<semicolon-separated TP IDs|无>`. TP IDs must be on the same line after the colon. Forbidden classification forms include `TP-X(变体测试点)`, `[变体测试点] TP-X`, `【变体测试点】:TP-X`, pipe-delimited annotations, tables, or nested TP lists. Before returning, verify that the Case `### 测试点` exact set equals the pairwise-disjoint union of the three canonical binding lines; do not add, remove, rename or duplicate a TP to make the format pass.\n\nScenario Partition slots: requiredVariantSlots for this module are `{{item.requiredVariantSlots}}`. Every listed ID MUST appear in this module's Cases as exactly one variant Test Point in both `### 测试点` and `变体测试点`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: one Case may carry many slots; do not create one Case per enum value just to match Case count. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.\n\nFor every variant Test Point, write its machine-checkable `场景意图: <TP-ID>; operation=...; target=...; intent=...` line inside that same Case body/自动化映射. Never collect Scenario Intent lines in a file-level appendix, implementation-details block, or another Case; local TP ownership is mandatory.\n\nEvery Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.\n\nIn every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Each Case describes exactly one target request payload contract: put every payload label on its own list line, never concatenate multiple operations or setup POST/PUT contracts into one label line, and never repeat a `Payload Contract:` token inside explanatory prose/details after the machine-readable line. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. If the bound contract only says an identifier is returned/present, do not declare a concrete identifier type. If a 404 Case needs a nonexistent path identifier but its syntax/type is unspecified, define a create-delete-derived valid identifier journey instead of an arbitrary UUID/text placeholder. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
249
249
  "outputContract": "Write exactly the frozen `{{item.markdownPath}}` Chinese module Markdown case-card file with BE-<MODULE>-<NNN> cases and the seven required h3 sections; keep machine IDs/literals exact and do not execute pytest or modify production code/config or the README.",
250
250
  "toolProfile": "write",
251
251
  "writePolicy": "exclusive",
@@ -293,7 +293,7 @@
293
293
  "materialize-backend-md-module-manifest-shell"
294
294
  ],
295
295
  "complexity": "MED",
296
- "subtask_prompt": "Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Read the immutable run-owned Markdown plan from the direct upstream manifest's planReadPath. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the plan Coverage Matrix; open and edit only modules that own a missing or conflicting rule. Never create or edit testcase/md/README.md and never modify the run-owned plan artifact. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.\n\nOutput budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect the immutable run-owned plan first, build a concise target list from its Matrix and Module Index, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable and located inside that same Case body/自动化映射, never in a file-level appendix, implementation-details block, or another Case. Use an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target. For intent=missing/empty/default-omit, pytest may use `_OMIT` or delete the key; for intent=enum-invalid use a concrete invalid enum literal (for example `UNKNOWN_STATUS`), never `_OMIT`/missing-key; for trim/padded samples use `custom-literal:trim` or a real padded string, not a bare token like `filter-active` when the intent is `custom-literal:ACTIVE`.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in the immutable run-owned plan artifact, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, preserve every frozen Module Index filename exactly (never rename an explicit-user-layout module; model-derived invalid stems must have been rejected before map expansion), normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nThis is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.\n\nFor affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.\n\nScenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.\n\nBefore returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in the run-owned plan or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.\n\nRead only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
296
+ "subtask_prompt": "Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Read the immutable run-owned Markdown plan from the direct upstream manifest's planReadPath. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the plan Coverage Matrix; open and edit only modules that own a missing or conflicting rule. Never create or edit testcase/md/README.md and never modify the run-owned plan artifact. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.\n\nOutput budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect the immutable run-owned plan first, build a concise target list from its Matrix and Module Index, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable and located inside that same Case body/自动化映射, never in a file-level appendix, implementation-details block, or another Case. Use an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target. For intent=missing/empty/default-omit, pytest may use `_OMIT` or delete the key; for intent=enum-invalid use a concrete invalid enum literal (for example `UNKNOWN_STATUS`), never `_OMIT`/missing-key; for trim/padded samples use `custom-literal:trim` or a real padded string, not a bare token like `filter-active` when the intent is `custom-literal:ACTIVE`.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in the immutable run-owned plan artifact, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, preserve every frozen Module Index filename exactly (never rename an explicit-user-layout module; model-derived invalid stems must have been rejected before map expansion), normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nThis is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.\n\nFor affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.\n\nScenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: keep existing Case structure and add missing exact variant Test Points to already-assigned Cases instead of creating one Case per enum value. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.\n\nBefore returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in the run-owned plan or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.\n\nRead only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
297
297
  "executor": "pi",
298
298
  "role": "implementer",
299
299
  "toolProfile": "write",
@@ -392,7 +392,7 @@
392
392
  "writePolicy": "read-only",
393
393
  "shell": {
394
394
  "commands": [
395
- "node -e \"eval(require('zlib').inflateRawSync(Buffer.from('pVptc9s2Ev6eX0E3GYOsSMpOr7lWKu3JW1vfJXHGTtq5kxQPTEISziTIAKBlxfR/v1mAIEFKst3pF4kEFovdxe6DxYJxzoR05iLi5GtJOXHRXCDPL7Bctk3whjw/5utC5m2zfkee/y2ll20zvCFv/CRWrLPkDeXRd5IIGWNBhlnynekSMaeF7HS3fZLTWL7D67yUESvTdFvH+RI///FFp5uIOBLRkQg5KVIcE3c4mU6ms9s71/t+cPzlWTWdzoYLH02nz/ZbIfnN++QjqMzIyjkji7c3hUtE7CrhvQGaTofu5GXwXxx8Owh+vgiD2cCbTsMsQT5aWGxKBtoUPI+JECFh1+HvL88+vD0/v3jz8reLs88fLt6cnFUVQuMndO7uaXrv1gwQMiGchytOJXFRRoWgbOFsYeHMc+5c4viKsCQA2znvMb9K8hVzihQzB3NJ5ziWU4a8cSPODZXuc298V0sLlEppWN7wfzllrhbIRwvCCMeSBGaOLAmAPCgo8hE8ge6e1mKuWAspztcsdg1X7yG1HiH+yEEDw2+AdiiTEulwgpOMRHNYdpz8SlPSkcVHpZz/1K4Ty3kGXnIuOWULV3ihzN/lK8JfY0Fcz/KdL3rFZwPwmQtkdX25GFQXg2fQbjdfGEoz16WM9DThnOfZ6yXmr/OEuD+/8GyPLl7h+ErS+Epo7xVFSqV7KT29LqjlR9k1TmlyRrDIWcTxKjq6NXwiUMzleOWN6dwdfikmB8Hz2bNhCDZ2hfQ8TmTJmYMKTnNO5TrIWboOsjwpUxIISTKkR05wMJ/BDyh/+8J/8cPdNjZ5gb+WJFhisdxgImQURUivDGpGcCIIvybJBvmemvQbTKpibPb9tglr7QOxZhLf1MK6x6NLUsmiwnHFydfqknuTi2A2pJvjAWOClF6R7vx1twKSO2NpNZO2cMfoysBR1IEdfnNG0neUXUXD6WTyZTqbDaazqTsNtwFHmCVTb7gwY3EKIwlAMNiq9aUpnzKFVcz2sClv2rSX6MComS0JTihbiGgyG89z7kJ00OhgTH8x04QpYQu5HNPBwLulc9e0T+gslJxmLuiGnj513isLOScsITfIM4zDohRLl0Lk0bnbtGqme1F0uCPu+6RRFB0cGzAwq0HVXCOUlEVKYwCgToc3QGOn3mcccoNjma6dnNXPTk9mR5BY0pzdC4JCYi4jI9vkYDY4HIPNCEuivsVae6pR99h0+OXp06kYTM9rF960sefdwhR0fMkJvhrfNfJomdu5RUpj4qoJfcISAwj2moOLTmbmLVW+VPOxXSSc01QS7qbRURpSFqdlQoSLKuQ1nGKSpiICDtER/JrhFTibEuTQDw69MMOFe43TkkRHXfTSrZ5RstmVbwoSS5L8TnBCeDRB9TqdQ/T56FUpQGjhnBGRlzwmyEenK0YS57SAfYjmTDRNZzDw32QNLYDXzskbeDwHSR0dochHzYYC+I989HGt9hn1NrODhXDlLEppEc4pS9S7q41QQyvPV5GyjWr2DF501QrJNeFrV5vAVx7rRUc8X03U8yyKIm2d8Z3eOa3pfznYtV0axLMDIdAjR91YcC6NGfMVI1wsaeFgljiwvTtxnpYZE/fGQoIlPstXoraFXnBLyMFzvfLKEo07gW1AyzoGjqKf9vdB6YOZ+d+Los6Cexby5SuSqKVTqdc5ke4EkRuIfiqDUhAepCrVg7SD0wzzdWDUDHjrLWCWgrCEMLm1Py9lUUJXsiASzRoJcuNfp8pkSoj3uHC9cR0QmOWMxjg9J4Q1IprehMQp5iTRyinMBXyAtOOMFJjyl0WRUpJEc5wKMn4CANI4lJPPG4t7t08cpwlmMFKdNLjagpAymqDyjVo2yWGXpFFL2ETPa6I6qMeoE8jqzwSuWdxXeZ4SzDxfjbGZ/diZcdzInyXvsYyXYI0wDK0BL+oBGXS/TFPXJN3eDEbTudsMfXg3QXU4ZHWYB+DkQUaF4g55o1sbsqpQya5YvmK7t49mlZ0ObOwIFseBRYbEQedbjdiTg9nkcOb5RiiVXHd6D4yq9ilmf3/Pfg21aiIUeUZcCtMcwW/YYRtF9isk2xtOJ3lJwFElZSVRcqszR+09D6Tnm1EERgWldybiir/KxjgRRc4EqeCh4mTBiRA0Z1WRCyrpNakYWWD1cJmXLMF8XRHOc17FeXaZV9r5PPd4NLlwgln1zDO5XCP9LvGBKihKXuSC3KeDExw5aGDa79dpz8apcImFq6Jhpww9yFbEAVc7U18A1Xf/7G0sW5nTQ6sHO0ASNEMfsXYKuNQmqfzLCtx/1oHbHn0grR12zz3TKWSlQ+TVDg7pvJX3DoPZYBoWa5PTtzN5VdW+WGlJGKLHGliPVxDQt2/L+h7Va+DVwJC8xiyhCZaQRFlhebw1RmuYfGSUjibb439z6kcg4PvTN5/fvb149/I/p58/Xbw+/fDru5PXn0YOy52S0a8laTRytLSqaNABOAcNbPkebaJoU+AW2vZM5/6+OY4CDnt7UQQr4+0AKj3YjFUboqNB1rSF8DZW7XTu/kXeXdE6qxSq5Fr8SeVSF3wG4MdV1VJbDmrRNuWrmn47865HW1R/2et3Lbj2JZXmCScvpaAJceY8/0aYw/NcCrXwDwKA3oIt0feiaKtKdsDaNG0raKmB1e43aGyS/Kr61/nph1AopKHztYV0sKC9zoaNgrb2XOBVlQJReybVoI8COzdGrXInWrdqO7ZAcYuqjW9tmxwO0t3Ms/ZrE1AFz/O5bSEg+giNdYZZEMhKHwk91vD9/X5LuOB5WZwkURQV9rs3riWC+FA9VaUJiJA0w5Ikp0oFKF2JX+rBGb6xWj8S/if4KK+q9+DPMaGpu5vJUBFl+MY99JWCNdp53tED3DV1PzGytaSsMHXhPaOp1VZVGyNqCXP+B+GQpDTD+h07A/Pkwx8v3528uTj9/Onj508Xrz6/+e3tp4uPZ6envz5i43Uc+G1BVil2C4M6iaTf+pzfj6SR8aDjncE2MhHp9+Jny9g+RRuXejCcwOEAPrKShB/+xtkCinInSYfdP/7uUUUH4Bbl7F717Idh6LZ71rZgPL61XkbbKO5Gt3feXb2JdQ6NKl9UTqd2qF0+1Fa8mtF2iRL8qGECx5h6S6dw4qVy7WQl1JpJvfXv3sc7ouEksUQD6Xsn2rrYZ3rb82vjEnCKVSz6sFxXTnQpIuqds8MFkS3ce1U1mY01pZ5Ru79anpEWsF21O2/c5yZsbr5mBPqqEqUNntY5u86UH4Gte32jKPzBsSxxGh3pf2XBKIqshTaHWj2Pgbi/uLer6xBzY9LL5wSkbzV3CA4LEZUEdaXQ1+fehEjCM8qokDR2PsJlDtT+Ha62RnAMlqvbBcKkqrENVXGtKSXtcCll4o0t1rKzxJcpiSaoctCgVy/T8jmVAxI6FfJRtUEDirledISCIEBGpQoGVAgit782rSHUjJPGHH4N/F1Y9Lf5bj3L2EGe1W9Qb6O3hq+NdstnfTTpxO/MDYedhjBLPBPiHdRXLS30zzaM5s06FWHH3IVB9aVXRD7wO3Vuz1dr428SQp25z3YutK9uXq7pCds7tjurzjXph+UMAKMXv6pkXod/XUQ83N/fq1t0VVW9REfqT1sWkqutlcKq6lH1UrBdMZhfE57iolDnZyNhDcICALhpNKfLWkLwua58m+GXEb4gzuW6LdKaDdnJuVMK4sC9ZymXcC2nSiPONuW2R6EKwr0NoGuXAQq6YHhV2K1z0Pp6EnMS9Wr4usatiFoWGYwH6i0FPO+W45VG7gwqYGOT0fSGm0sJi0N9bbaNxV33xtPcaNVHUeAHF3GmQq8T7t5FnboH1V1e3WVtMCN9hoSKar2xKEMaQoPY7aQQjGrD0xTeVj9SEas5DtDGvq1WGm6dLlOy6QvDJM8wZerce2+pPmsrzk825euBooovJbGdiGSbW7xS37Q/XGwiWSHXnYuJ9kYCSyclGBIARjbU1UN2KLghwtFPOwW4gUZ63VwTxnnJ1FcDXQ4D5Bw5P1lBKJfEERlOU6KvAO9fDPLwdxRnBCfttxScpCqEXTMkXiWu5zefRtRpraIVpDBQMWxvRoC0/rpFf2UTxpxgSX7HYukioXqQF5YFFF9cjcBemNAFFNfQEu5Kx08so0Fyo43WO1bfGnzLNjZQ9zYMQ3j0bQ3vPK/z7rei+pqHRqDRrYiXJMMnyQjZn3qYpdJ4FsBnHiK4PkR+fU7p1NyQxOIqgBo2x7FEI/XpSZAQTq9JgnzToacfbX4aBBJ1We7v9xM+UlVoo1Dc3DpZqY2U4PCvlYttZDzHh6MDi/i0lHGekS10KF5itiAJGiGWQ1lY53To7g5Sxv8D','base64')).toString('utf8'))\""
395
+ "node -e \"eval(require('zlib').inflateRawSync(Buffer.from('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','base64')).toString('utf8'))\""
396
396
  ],
397
397
  "timeoutMs": 60000,
398
398
  "cwd": "."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.42.0-next.5",
3
+ "version": "0.42.0-next.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",