@tea-agent/loop-agent 0.29.3 → 0.30.0

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.
@@ -3254,194 +3254,6 @@ function buildReviewBackendCasesNode(sources, options) {
3254
3254
  .join("\n\n"),
3255
3255
  };
3256
3256
  }
3257
- function buildReviewBackendCasesGateNode(sources) {
3258
- return {
3259
- id: "review-backend-cases-gate-shell",
3260
- // OR-join tips: pass-path barrier vs final review after one revise.
3261
- // Soft condition-skip on the unused tip still allows the gate to run.
3262
- depends_on: [
3263
- "review-backend-cases-branch-condition",
3264
- "review-backend-cases-final-pi",
3265
- // Always finished; also required for verdictGate.fallbackFromNodeIds validate.
3266
- "review-backend-cases-pi",
3267
- ],
3268
- dependsPolicy: "all-or-condition-skip",
3269
- role: "verifier",
3270
- executor: "shell",
3271
- complexity: "LOW",
3272
- writePolicy: "read-only",
3273
- allowedPaths: commonReadOnlyPaths(sources),
3274
- forbiddenPaths: commonForbiddenPaths(sources),
3275
- outputContract: "Deterministic backend case review gate: exit 0 only when the effective review emits VERDICT: pass (sole authorization for generate-backend-pytest-pi). Prefers final review JSON when present (revision path); else first review (pass path).",
3276
- subtask_prompt: "Deterministic gate: block pytest generation unless the effective backend case review (final after revision, else first) emitted VERDICT: pass.",
3277
- shell: {
3278
- commands: [],
3279
- verdictGate: {
3280
- // Prefer final (revision path) when its artifact exists; fall back to first review.
3281
- fromNodeId: "review-backend-cases-final-pi",
3282
- fallbackFromNodeIds: ["review-backend-cases-pi"],
3283
- accept: ["VERDICT: pass"],
3284
- label: "backend case effective review",
3285
- lineMode: "first-verdict-line",
3286
- },
3287
- cwd: ".",
3288
- timeoutMs: 60000,
3289
- },
3290
- };
3291
- }
3292
- function buildGenerateBackendPytestNode(sources) {
3293
- return {
3294
- id: "generate-backend-pytest-pi",
3295
- depends_on: [
3296
- "backend-test-case-manifest-shell",
3297
- "validate-backend-test-contracts-shell",
3298
- ],
3299
- role: "implementer",
3300
- executor: "pi",
3301
- toolProfile: "write",
3302
- complexity: "HIGH",
3303
- writePolicy: "exclusive",
3304
- // test_*.py plus optional helpers/factories under testcase/ (not conftest/config)
3305
- writeSet: [
3306
- "testcase/**/test_*.py",
3307
- "testcase/**/helpers/**",
3308
- "testcase/**/factories/**",
3309
- ],
3310
- // Union task allowedPaths with testcase/** so writeSet stays in scope even when
3311
- // task.json only lists product paths (e.g. ./src/**). Writes still gated by writeSet.
3312
- allowedPaths: Array.from(new Set([...commonReadOnlyPaths(sources), "testcase/**"])),
3313
- forbiddenPaths: commonForbiddenPaths(sources),
3314
- // 注意:Pi 节点超时由 executor 层控制(默认 30 分钟)
3315
- // 如需调整,在 harness.json 的 executors.pi 中配置 modelConfig.timeoutMs
3316
- subtask_prompt: [
3317
- "Convert the validated test cases under testcase/md/ into pytest automation code.",
3318
- "",
3319
- "## Inputs (MUST use validated contracts):",
3320
- "- Validated cases under testcase/md/ and contracts/backend-test-case-manifest.json (case review runs independently as advisory evidence).",
3321
- "- Validated Backend Test Analysis v2 under contracts/backend-test-analysis.json.",
3322
- "- Validated Backend Test Execution Contract v1 under contracts/backend-test-execution.json (execution gate).",
3323
- "Use only fixture names, env NAMES, testRoot, targetMode, and field/API facts already present in those contracts or reviewed cases. Do not invent production credentials or secret values.",
3324
- "When targetMode is in-process (including demoted local npm/node managed servers): bootstrap the service inside function-scoped pytest fixtures under testcase/** — e.g. subprocess node server.js / startWelcomeServer with PORT=0 — and never require host-injected base URL env vars (clean-env shell will not provide WELCOME_BASE_URL / API_BASE_URL).",
3325
- "Do not depend on requiredEnvNames being present at process start for in-process mode; if the contract still lists an env name, the fixture must set it or start the server without that env.",
3326
- "",
3327
- "## Output Steps (do in order):",
3328
- "1. First, output a brief summary: how many files, how many test functions planned",
3329
- "2. Then write each test file under testcase/",
3330
- "",
3331
- "## Format Rules:",
3332
- "- File prefix: test_<module>.py",
3333
- "- Function name: test_BE_<MODULE>_<NNN>_<description>",
3334
- "- Docstring first line: BE-<MODULE>-<NNN>: <Case Title>",
3335
- "- 1:1 mapping: each functional case → one pytest function",
3336
- "",
3337
- "## Implementation Rules:",
3338
- "- Use assert statements, not unittest assertions",
3339
- "- Use @pytest.mark.parametrize for boundary cases when the case defines edge values",
3340
- "- Use markers: @pytest.mark.positive, @pytest.mark.negative, @pytest.mark.boundary",
3341
- "",
3342
- "## Test Data Preparation Rules (MUST follow):",
3343
- "",
3344
- "### When Setup is Needed",
3345
- "Setup phase is REQUIRED only when test cases need pre-existing data:",
3346
- "- Query/Read APIs: need data to exist before querying",
3347
- "- Update/Delete APIs: need data to exist before modifying",
3348
- "- State transition tests: need data in specific state",
3349
- "",
3350
- "Setup phase is NOT needed for:",
3351
- "- Create APIs: testing the creation itself",
3352
- "- Validation tests: testing input validation with invalid data",
3353
- "",
3354
- "### Data Setup Strategy",
3355
- "When setup is needed:",
3356
- "1. Prefer function-scoped fixtures for isolation; use module/session scope only when cases explicitly share immutable fixtures",
3357
- "2. Prefer API-based setup from the upstream analyze-inputs-pi API list and reviewed cases",
3358
- "3. If a required helper/factory is missing, create NEW files only under testcase/**/helpers/** or testcase/**/factories/**",
3359
- "",
3360
- "### Data Construction Priority",
3361
- "1. API-first: construct data via documented APIs from analyze-inputs-pi / reviewed cases",
3362
- "2. Reuse existing conftest fixtures when present (read-only)",
3363
- "3. Direct DB writes are LAST RESORT and only if conftest already exposes a safe test DB fixture with rollback/isolation",
3364
- "4. If neither API nor safe DB fixture exists, skip the case with an explicit gap note — do NOT invent production DB credentials or write live data",
3365
- "",
3366
- "### API Data Construction",
3367
- "- Prefer the analyze-inputs-pi API Endpoints section and reviewed cases for method/path/fields",
3368
- "- Chain API calls only when cases document multi-step preconditions",
3369
- "- Store created resource IDs in fixtures for reuse",
3370
- "- Do NOT broadly search host route/controller trees for secrets, .env, private keys, or production configs",
3371
- "- Read host API definitions only when needed to resolve a field name already referenced by reviewed cases; stay out of credential/config paths",
3372
- "",
3373
- "### Database Data Construction (restricted)",
3374
- "- Allowed only via existing conftest test-DB fixtures with transaction rollback or equivalent isolation",
3375
- "- Never hardcode connection strings, passwords, tokens, or cloud credentials",
3376
- "- Never target production/shared non-test databases",
3377
- "- If isolation is unclear, report the gap instead of writing DB rows",
3378
- "",
3379
- "## Assertion Rules (MUST follow):",
3380
- "",
3381
- "### Positive Path",
3382
- "MUST assert ALL of the following:",
3383
- "1. HTTP status code: as defined in API spec (e.g. 200, 201)",
3384
- "2. Response structure: key fields exist in response body",
3385
- "3. Specific values: each field equals expected value from test case",
3386
- "4. Data type: each field is correct type",
3387
- "",
3388
- "### Negative Path",
3389
- "MUST assert ALL of the following:",
3390
- "1. HTTP status code: as defined in API spec (e.g. 400, 404, 500)",
3391
- "2. Error code field: field name from API spec (e.g. code, error_code, errcode, ret)",
3392
- "3. Error message field: field name from API spec (e.g. message, msg, errmsg, error)",
3393
- "",
3394
- "### Field Name Resolution",
3395
- "Field names MUST come from the upstream analyze-inputs-pi output (API Endpoints section) or reviewed cases, NOT guessed. For example:",
3396
- '- If API spec defines {"ret": 0, "msg": "success"}, assert response.json()[\'ret\'] and response.json()[\'msg\']',
3397
- '- If API spec defines {"code": 4001, "message": "error"}, assert response.json()[\'code\'] and response.json()[\'message\']',
3398
- "",
3399
- "## Conditional Implementation (include ONLY if test cases exist):",
3400
- "- Authentication tests: implement ONLY if testcase/md/ contains auth-related cases",
3401
- "- Timeout tests: implement ONLY if testcase/md/ contains timeout-related cases",
3402
- "- Boundary tests: implement ONLY when cases define value ranges, length limits, or format constraints",
3403
- "- Use @pytest.mark.auth for auth tests, @pytest.mark.timeout for timeout tests",
3404
- "- If no such cases exist, do NOT add these tests",
3405
- "",
3406
- "## Constraints:",
3407
- "- Only create NEW files under writeSet: testcase/**/test_*.py, testcase/**/helpers/**, testcase/**/factories/**",
3408
- "- Do NOT modify existing framework files (conftest.py, pytest.ini, pyproject.toml, setup.cfg, __init__.py)",
3409
- "- If a test filename exists, add suffix: test_order.py → test_order_01.py",
3410
- "- Do NOT re-read source documents — use reviewed cases under testcase/md/ and upstream analyze-inputs-pi output only",
3411
- "- Read existing conftest.py/pytest.ini to understand conventions, but do NOT modify them",
3412
- "- Do NOT execute pytest/python -m pytest or npm test in this node; the single execution is owned by the dedicated shell node. Local smoke runs create __pycache__/.pytest_cache and are unnecessary here.",
3413
- ].join("\n\n"),
3414
- };
3415
- }
3416
- function buildBackendTestSemanticReviewNode(sources, options = {}) {
3417
- return {
3418
- id: "review-generated-backend-pytest-pi",
3419
- depends_on: options.dependsOn ?? [
3420
- "generate-backend-pytest-pi",
3421
- "backend-test-analysis-contract-shell",
3422
- "backend-test-case-manifest-shell",
3423
- ],
3424
- role: "reviewer",
3425
- executor: "pi",
3426
- complexity: "MED",
3427
- writePolicy: "read-only",
3428
- allowedPaths: ["testcase/**"],
3429
- forbiddenPaths: commonForbiddenPaths(sources),
3430
- outputContract: "Pure Backend Test Semantic Review v1 JSON: verdict, findings[], summary. No file writes.",
3431
- subtask_prompt: [
3432
- "Review generated pytest semantics before the single execution.",
3433
- "Use only compact authoritative inputs: contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, testcase/md/**, and generated testcase/**/test_*.py/helpers/factories.",
3434
- "Return exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.",
3435
- "verdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.",
3436
- "severity must be exactly Critical, Important, or Informational; contractRefs must be a non-empty string array. A request-revision verdict requires at least one finding; pass must not contain Critical findings.",
3437
- 'Minimal shape: {"verdict":"pass","findings":[],"summary":"No contract-backed semantic contradiction found."}',
3438
- "Check responseBody.kind (array vs object/items), ordering, field comparison (especially parseable-only date-time precision), documented status/error fields, and each caseId→symbol assertion meaning.",
3439
- "Do not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.",
3440
- "request-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.",
3441
- "Read-only; do not edit tests or production code.",
3442
- ].join("\n\n"),
3443
- };
3444
- }
3445
3257
  function collectBackendTestShellEnvAllowlist(sources) {
3446
3258
  const names = new Set();
3447
3259
  const collectAssignments = (text) => {
@@ -3671,6 +3483,46 @@ const BACKEND_TEST_DEFAULTS = {
3671
3483
  ...HYBRID_DEFAULTS,
3672
3484
  writePolicy: "read-only",
3673
3485
  };
3486
+ /**
3487
+ * Builds the `node -e` command for the backend-test module manifest shell.
3488
+ * The inline JS mirrors `extractModuleStemsFromReadme` so the map_agent
3489
+ * shard set deterministically matches the README index the Completeness
3490
+ * Gate trusts: only table-row `testcase/md/<stem>.md` mentions and canonical
3491
+ * relative links `[label](./<stem>.md)` count, with the same
3492
+ * `looksLikeValidModuleStem` filter and `normalizeBackendTestModuleStem`
3493
+ * normalization. Output is exactly one trailing JSON line `{modules:[{stem}]}`
3494
+ * that `parseJsonFromText` accepts after shell command echoes.
3495
+ */
3496
+ function buildBackendTestModuleManifestShellCommand() {
3497
+ // The extractor is base64-encoded so the shell command is fully opaque to
3498
+ // bash: no backticks (command substitution), no regex \/ escaping, no
3499
+ // backslash-counting through TS-string -> JSON.stringify -> bash -c -> node -e.
3500
+ // Backticks in the README body are stripped at runtime via
3501
+ // String.fromCharCode(96), so the extractor source contains no backtick.
3502
+ const script = `const fs=require('fs');
3503
+ const readme=fs.existsSync('testcase/md/README.md')?fs.readFileSync('testcase/md/README.md','utf8'):'';
3504
+ const norm=s=>String(s).toLowerCase().replace(/[^a-z0-9]+/g,'_').replace(/^_+|_+$/g,'').replace(/_+/g,'_');
3505
+ const bt=String.fromCharCode(96);
3506
+ const stripBackticks=s=>s.split(bt).join('');
3507
+ const valid=raw=>{const st=norm(raw);if(st==='readme')return false;if(!/^[a-z][a-z0-9_]*$/.test(st))return false;if(/^(?:be|tp|ac|req|br)[_-]/i.test(st))return false;return true;};
3508
+ const rxMdPath=/testcase\\/md\\/([A-Za-z0-9_.-]+)\\.md/g;
3509
+ const rxTableRow=/\\|\\s*([A-Za-z0-9_.-]+)\\s*\\|\\s*testcase\\/test_/g;
3510
+ const rxRelLink=/\\[[^\\]]+\\]\\(\\.\\/([A-Za-z0-9_.-]+)\\.md\\)/g;
3511
+ const raw=[];
3512
+ const lines=readme.replace(/\\r\\n/g,'\\n').replace(/\\r/g,'\\n').split('\\n').filter(l=>l.includes('|'));
3513
+ for(const line of lines){
3514
+ const bare=stripBackticks(line);
3515
+ for(const m of bare.matchAll(rxMdPath)){if(valid(m[1]))raw.push(m[1]);}
3516
+ for(const m of bare.matchAll(rxTableRow)){if(valid(m[1]))raw.push(m[1]);}
3517
+ }
3518
+ for(const m of readme.matchAll(rxRelLink)){if(valid(m[1]))raw.push(m[1]);}
3519
+ const seen=new Set();const modules=[];
3520
+ for(const r of raw){const st=norm(r);if(!seen.has(st)){seen.add(st);modules.push({stem:st});}}
3521
+ process.stdout.write(JSON.stringify({modules}));
3522
+ `;
3523
+ const encoded = Buffer.from(script, 'utf8').toString('base64');
3524
+ return `node -e "eval(Buffer.from('${encoded}','base64').toString('utf8'))"`;
3525
+ }
3674
3526
  const BACKEND_TEST_SKILLS_BY_ROLE = {
3675
3527
  planner: ["loop-agent"],
3676
3528
  scout: [],
@@ -3710,40 +3562,41 @@ async function buildBackendTestHybridDag(sources) {
3710
3562
  "python -m pytest --version",
3711
3563
  "python -m pytest --help",
3712
3564
  ]);
3713
- const generateCases = {
3714
- id: "generate-backend-md-cases-pi",
3565
+ // N2 line (sharded): README plan → manifest shell → map_agent barrier.
3566
+ // Each module Markdown card is written by an independent Pi child session
3567
+ // with its own 16K token budget, isolating single-large-file truncation risk.
3568
+ const generateMdPlan = {
3569
+ id: "generate-backend-md-plan-pi",
3715
3570
  depends_on: [environment.id],
3716
3571
  role: "implementer",
3717
3572
  executor: "pi",
3718
3573
  toolProfile: "write",
3719
3574
  complexity: "MED",
3720
3575
  writePolicy: "exclusive",
3721
- writeSet: ["testcase/md/**"],
3722
- allowedPaths: ["testcase/md/**"],
3576
+ writeSet: ["testcase/md/README.md"],
3577
+ allowedPaths: ["testcase/md/README.md"],
3723
3578
  forbiddenPaths: forbidden,
3724
3579
  writerOutcomePolicy: {
3725
3580
  type: "implementation-outcome-v1",
3726
3581
  requireChangedFiles: true,
3727
3582
  },
3728
3583
  retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
3729
- outputContract: "Write a Chinese, human-readable testcase/md/README.md plus module Markdown case cards using BE-<MODULE>-<NNN>; keep machine IDs/literals exact and do not execute pytest or modify production code/config.",
3584
+ outputContract: "Write a Chinese, human-readable testcase/md/README.md as the single Markdown-first entry page with Coverage Scope, Coverage Matrix and a machine-parseable module index. Do not write module case cards here; do not execute pytest or modify production code/config.",
3730
3585
  subtask_prompt: [
3731
- "This is a required file-generation node. After reading the bounded inputs, immediately use write/edit tools to create testcase/md/README.md and the module Markdown files. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists.",
3732
- "Output budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file. Order: README.md (Scope+Matrix+module index only) → one module file per turn → short IMPLEMENTATION_OUTCOME. Splitting modules preserves every in-scope rule/TP; it must not drop coverage. 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.",
3733
- "The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the required files have been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.",
3734
- "Read the upstream environment report. Generate a Markdown-first backend test strategy and cases under testcase/md/**.",
3586
+ "This is a required file-generation node. After reading the bounded inputs, immediately use write tools to create testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Write ONLY testcase/md/README.md in this node; module case cards are written by downstream sharded nodes.",
3587
+ "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.",
3588
+ "The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the README has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.",
3589
+ "Read the upstream environment report. Generate the Markdown-first backend test README under testcase/md/README.md.",
3735
3590
  "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.",
3736
3591
  "Create testcase/md/README.md as the concise 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 README.",
3737
- "Before the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README 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`. Classify from authoritative task/reference evidence, not merely whether a route already exists. Use only these pairs: `new-operation` `full-contract`; `contract-change` `affected-contract-full`; `behavior-change` `affected-behavior-full`; `bugfix` `reproduction-plus-neighbors`; `implementation-optimization` `change-focused-plus-regression-floor`. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.",
3738
- "Coverage depth follows the declared change scope. For `new-operation`, fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected new operation, but do not re-test unrelated existing operations. For contract/behavior changes, fully cover the changed contract or behavior and its directly affected operations. For bugfix, cover exact reproduction, adjacent boundary/equivalence cases and a normal path. For `implementation-optimization`, cover explicit ACs, deterministic affected operations and a minimum regression floor; do not exhaustively regenerate unrelated POST/PUT/GET/DELETE rules. Every non-new classification must include `main-success-path` and `unchanged-response-shape`; contract changes also include `changed-contract-boundaries`, behavior changes `affected-state-transition`, and bugfixes `defect-reproduction` plus `adjacent-boundary`. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same changed path can affect them; unresolved impact stays visible as GAP/CONFLICT.",
3592
+ "Before the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README 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.",
3593
+ "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.",
3739
3594
  "Before writing cases, build the mandatory machine-readable Coverage Matrix inside `testcase/md/README.md` 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.",
3740
3595
  "Each 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.",
3741
3596
  "Coverage 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.",
3742
3597
  "For 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.",
3743
- "Write each module as readable case cards. 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 `### 自动化映射`; `覆盖规则` 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.",
3744
- "Name each module file with a stable lowercase business stem such as `testcase/md/health.md` or `testcase/md/resource_notes.md`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` `testcase/test_health.py`; `testcase/md/resource_notes.md` `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
3745
- "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 ‘符合预期’.",
3746
- "In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`. 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 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. Before finalizing Markdown, 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.",
3598
+ "Mandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` `testcase/test_health.py`; `testcase/md/resource_notes.md` `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
3599
+ "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.",
3747
3600
  intake.boundedSourceContext,
3748
3601
  "## Authoritative reference index",
3749
3602
  JSON.stringify(intake.referenceIndex, null, 2),
@@ -3751,9 +3604,81 @@ async function buildBackendTestHybridDag(sources) {
3751
3604
  "Read only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
3752
3605
  ].join("\n\n"),
3753
3606
  };
3607
+ const materializeMdManifest = {
3608
+ id: "materialize-backend-md-module-manifest-shell",
3609
+ depends_on: [generateMdPlan.id],
3610
+ role: "verifier",
3611
+ executor: "shell",
3612
+ complexity: "LOW",
3613
+ writePolicy: "read-only",
3614
+ allowedPaths: ro,
3615
+ forbiddenPaths: forbidden,
3616
+ outputContract: "Stdout JSON {modules:[{stem}]} parsed from testcase/md/README.md using the same module-stem extractor as the Completeness Gate, so the map_agent shard set deterministically matches the README module index.",
3617
+ subtask_prompt: "Parse testcase/md/README.md and emit exactly one trailing JSON line {modules:[{stem}]} listing every trusted module stem (table-row testcase/md/<stem>.md mentions and canonical [label](./<stem>.md) relative links only). No file writes.",
3618
+ shell: {
3619
+ commands: [buildBackendTestModuleManifestShellCommand()],
3620
+ cwd: ".",
3621
+ timeoutMs: 60000,
3622
+ },
3623
+ };
3624
+ const generateMdCasesMap = {
3625
+ id: "generate-backend-md-cases-map",
3626
+ depends_on: [materializeMdManifest.id],
3627
+ role: "verifier",
3628
+ executor: "static",
3629
+ complexity: "LOW",
3630
+ writePolicy: "none",
3631
+ allowedPaths: [],
3632
+ forbiddenPaths: forbidden,
3633
+ outputContract: "Serial aggregate of sharded Markdown module case-card writers. Each child writes exactly one testcase/md/<stem>.md with its own 16K Pi budget.",
3634
+ subtask_prompt: "Expand the README module manifest into one sharded Markdown writer child per module and run them serially. Child failures fail-close the map barrier.",
3635
+ static: { resultMarkdown: "Backend-test Markdown case-card map expansion barrier." },
3636
+ dynamicExpansion: {
3637
+ type: "map_agent",
3638
+ workflowNodeId: "generate-backend-md-cases-map",
3639
+ itemsFrom: "$.nodes['materialize-backend-md-module-manifest-shell'].output.modules",
3640
+ itemName: "item",
3641
+ maxItems: 64,
3642
+ maxExpandedNodes: 64,
3643
+ childIdPrefix: "generate-backend-md-case",
3644
+ tokenBudget: { maxTokensPerCase: 16384 },
3645
+ childTask: {
3646
+ executor: "pi",
3647
+ role: "implementer",
3648
+ skills: BACKEND_TEST_SKILLS_BY_ROLE.implementer,
3649
+ toolProfile: "write",
3650
+ complexity: "MED",
3651
+ writePolicy: "exclusive",
3652
+ allowedPaths: ["testcase/md/{{item.stem}}.md"],
3653
+ forbiddenPaths: forbidden,
3654
+ writeSet: ["testcase/md/{{item.stem}}.md"],
3655
+ writerOutcomePolicy: {
3656
+ type: "implementation-outcome-v1",
3657
+ requireChangedFiles: true,
3658
+ },
3659
+ retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
3660
+ outputContract: "Write exactly one Chinese module Markdown case-card file testcase/md/<stem>.md 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.",
3661
+ subtaskPromptTemplate: [
3662
+ "This is a required file-generation node for exactly one Markdown module. After reading testcase/md/README.md (Coverage Scope + Coverage Matrix + module index) and the bounded references, immediately use write tools to create the single file testcase/md/{{item.stem}}.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify README.md or any other module file.",
3663
+ "Output 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.",
3664
+ "The 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.",
3665
+ "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.",
3666
+ "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.",
3667
+ "Name this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `testcase/test_{{item.stem}}.py`, 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.",
3668
+ "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 ‘符合预期’.",
3669
+ "In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`. 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 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.",
3670
+ intake.boundedSourceContext,
3671
+ "## Authoritative reference index",
3672
+ JSON.stringify(intake.referenceIndex, null, 2),
3673
+ "For 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.",
3674
+ "Read only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
3675
+ ].join("\n\n"),
3676
+ },
3677
+ },
3678
+ };
3754
3679
  const reviewCases = {
3755
3680
  id: "review-and-revise-backend-md-cases-pi",
3756
- depends_on: [generateCases.id],
3681
+ depends_on: [generateMdCasesMap.id],
3757
3682
  role: "reviewer",
3758
3683
  executor: "pi",
3759
3684
  toolProfile: "write",
@@ -3767,7 +3692,7 @@ async function buildBackendTestHybridDag(sources) {
3767
3692
  "Independently review generated Markdown cases against the task requirements and environment evidence. Treat the files as human-facing test documentation: require clear preconditions, executable steps and assertable expected results; improve names, purpose, metadata and automation mapping where useful while preserving exact machine IDs and technical literals.",
3768
3693
  "Output budget protocol: default to local edit per file; never dump full Matrix/case bodies into assistant chat. Review order is README (Scope/Matrix) then one module file per turn. When adding omitted in-scope cases, write one file per tool call and keep every required section. Do not bulk-delete in-scope cases to save tokens.",
3769
3694
  "For every variant Test Point, ensure the Markdown scenario intent is machine-checkable: prefer an explicit line `场景意图: <empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal|custom-literal:V>; field=<name>; bound=<n optional>; example=<optional>` near 测试数据/操作步骤, and keep pytest params later aligned to that intent.",
3770
- "Independently reconstruct the change classification, affected operations/rules, P0 product scenarios and applicable P1 documented API rules from authoritative sources before trusting the generated Coverage Scope or Coverage Matrix. Perform an explicit coverage-scope review: reject `new-operation` when the task only optimizes an existing implementation without contract change; reject narrow optimization scope when shared validator/helper/DTO/query builder evidence directly affects more operations; reject full-contract expansion across unrelated operations. 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. Directly add in-scope omissions; undefined impact remains GAP/CONFLICT rather than invented behavior.",
3695
+ "Treat 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.",
3771
3696
  "Check 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 `testcase/md/README.md`, 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.",
3772
3697
  "Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, 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 case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol, assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, ensure every binding-list Test Point is also present in that Case's `### 测试点`, 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.",
3773
3698
  "Read only precise referenced source paths plus requirement sections needed for uncovered ACs. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
@@ -3778,33 +3703,30 @@ async function buildBackendTestHybridDag(sources) {
3778
3703
  ].join("\n\n"),
3779
3704
  };
3780
3705
  const validateCases = shellNode("validate-backend-md-cases-shell", [reviewCases.id], "markdown-cases", "Record advisory findings for Markdown structure and deterministically analyze the final README Coverage Scope and Coverage Matrix against final Case rule/test-point bindings. Validate the classification-policy pair, affected operations/rules, scope evidence and regression floor; require documented OpenAPI completeness only for declared affected operations, while all explicit AC/REQ/BR remain in scope. Detect missing in-scope product/API rules, enum values, invalid equivalence classes, boundaries, format classes, business lifecycle states, GAP/CONFLICT, bidirectional Matrix/Case drift, non-canonical Case IDs, unclassified Test Points, duplicate binding modes and non-cross-cutting Test Points bound by multiple Cases. Do not validate source-reference existence. Write human and machine evidence from the same facts. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected. Coverage FAIL stays advisory.", "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json v3 with Coverage Scope plus PASS/FAIL/UNAVAILABLE advisory facts; downstream execution continues.");
3781
- const generatePytest = {
3782
- id: "generate-backend-pytest-pi",
3706
+ // N5 line (sharded): pytest shared-asset plan → manifest shell → map_agent barrier.
3707
+ // Shared helpers/factories are written once by the plan node; each module's
3708
+ // test_<stem>.py is written by an independent Pi child (own 16K budget).
3709
+ const generatePytestPlan = {
3710
+ id: "generate-backend-pytest-plan-pi",
3783
3711
  depends_on: [validateCases.id],
3784
3712
  role: "implementer",
3785
3713
  executor: "pi",
3786
3714
  toolProfile: "write",
3787
3715
  complexity: "HIGH",
3788
3716
  writePolicy: "exclusive",
3789
- writeSet: [
3790
- "testcase/**/test_*.py",
3791
- "testcase/**/helpers/**",
3792
- "testcase/**/factories/**",
3793
- ],
3794
- allowedPaths: Array.from(new Set([...ro, "testcase/**"])),
3717
+ writeSet: ["testcase/**/helpers/**", "testcase/**/factories/**"],
3718
+ allowedPaths: Array.from(new Set([...ro, "testcase/**/helpers/**", "testcase/**/factories/**"])),
3795
3719
  forbiddenPaths: forbidden,
3796
3720
  retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
3797
3721
  writerOutcomePolicy: {
3798
3722
  type: "implementation-outcome-v1",
3799
3723
  requireChangedFiles: true,
3800
3724
  },
3801
- outputContract: "Convert every final automatable Markdown case into pytest assets whose actual test function region contains the exact Case ID, preferably in the function name or docstring. Each testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
3725
+ outputContract: "Write the shared pytest support assets (HTTP logging/redaction helper, request factories, shared fixtures) under testcase/**/helpers/** and testcase/**/factories/** only. Do not write per-module test_*.py here; those are written by downstream sharded nodes. No JSON and no pytest execution.",
3802
3726
  subtask_prompt: [
3803
- "Convert testcase/md/** to pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
3804
- "Output budget protocol (hard, max output <=16K per turn): Write helpers/factories first, then exactly one test_<module>.py per write/edit tool call following MD stems. Never paste full Python modules into assistant chat. Do not merge modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.",
3727
+ "Convert testcase/md/** into pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. This node writes ONLY the shared pytest support assets (HTTP logging/redaction helper, request factories, shared fixtures); each module's test_<module>.py is written by a downstream sharded node that imports these helpers. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
3728
+ "Output budget protocol (hard, max output <=16K per turn): Write helpers/factories one file per write/edit tool call. Never paste full Python modules into assistant chat. Do not reduce params/assertions/skips semantics to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken shared files.",
3805
3729
  "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.",
3806
- 'Ensure every final Markdown Case ID appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. 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 `pytest.param(..., id=...)` for every row; 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). 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 前置条件/测试数据/自动化映射.',
3807
- "Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, `testcase/md/health.md` maps to `testcase/test_health.py`, `testcase/md/BE-HEALTH.md` maps to `testcase/test_be_health.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes such as `test_be_*` unless the module filename itself normalizes to that stem. 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.",
3808
3730
  "Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. 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.",
3809
3731
  "HTTP response header names are case-insensitive. If the helper stores a lower-case normalized header map, every Content-Type or other header assertion must query the lower-case key (for example `content-type`) or use an explicitly case-insensitive accessor; never call a case-sensitive plain dict with `Content-Type` when the stored key is lower-case. Preserve the actual media-type assertion rather than dropping it.",
3810
3732
  "Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
@@ -3812,7 +3734,80 @@ async function buildBackendTestHybridDag(sources) {
3812
3734
  "Do not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`.",
3813
3735
  ].join("\n\n"),
3814
3736
  };
3815
- const collectionAssess = shellNode("assess-backend-pytest-collection-shell", [generatePytest.id], "markdown-collection-assess", "Resolve final Markdown-mapped scripts before any business test body execution. A safe deterministic mapped script that the pytest writer omitted is REPAIRABLE without starting pytest; otherwise run pytest collection only over existing mapped scripts. Materialize hash-bound PASS/REPAIRABLE/BLOCKED facts. Only missing mapped generated scripts and generated testcase-local syntax/import inconsistencies are repairable; dependency, plugin, production-module, environment, safety and unknown failures remain blocked.", "Run-owned reports/backend-test-pytest-collection-initial.md and contracts/backend-test-pytest-collection-initial.json with bounded diagnostics, asset hashes, collected item IDs and deterministic repair eligibility.", [], 120000);
3737
+ const materializePytestManifest = {
3738
+ id: "materialize-backend-pytest-module-manifest-shell",
3739
+ depends_on: [generatePytestPlan.id],
3740
+ role: "verifier",
3741
+ executor: "shell",
3742
+ complexity: "LOW",
3743
+ writePolicy: "read-only",
3744
+ allowedPaths: ro,
3745
+ forbiddenPaths: forbidden,
3746
+ outputContract: "Stdout JSON {modules:[{stem}]} parsed from testcase/md/README.md using the same module-stem extractor as the Completeness Gate, so the map_agent shard set deterministically matches the Markdown module index.",
3747
+ subtask_prompt: "Parse testcase/md/README.md and emit exactly one trailing JSON line {modules:[{stem}]} listing every trusted module stem (table-row testcase/md/<stem>.md mentions and canonical [label](./<stem>.md) relative links only). No file writes.",
3748
+ shell: {
3749
+ commands: [buildBackendTestModuleManifestShellCommand()],
3750
+ cwd: ".",
3751
+ timeoutMs: 60000,
3752
+ },
3753
+ };
3754
+ const generatePytestCasesMap = {
3755
+ id: "generate-backend-pytest-cases-map",
3756
+ depends_on: [materializePytestManifest.id],
3757
+ role: "verifier",
3758
+ executor: "static",
3759
+ complexity: "LOW",
3760
+ writePolicy: "none",
3761
+ allowedPaths: [],
3762
+ forbiddenPaths: forbidden,
3763
+ outputContract: "Serial aggregate of sharded pytest module writers. Each child writes exactly one testcase/test_<stem>.py with its own 16K Pi budget, importing the shared helpers/factories from the plan node.",
3764
+ subtask_prompt: "Expand the README module manifest into one sharded pytest writer child per module and run them serially. Child failures fail-close the map barrier.",
3765
+ static: { resultMarkdown: "Backend-test pytest module map expansion barrier." },
3766
+ dynamicExpansion: {
3767
+ type: "map_agent",
3768
+ workflowNodeId: "generate-backend-pytest-cases-map",
3769
+ itemsFrom: "$.nodes['materialize-backend-pytest-module-manifest-shell'].output.modules",
3770
+ itemName: "item",
3771
+ maxItems: 64,
3772
+ maxExpandedNodes: 64,
3773
+ childIdPrefix: "generate-backend-pytest-case",
3774
+ tokenBudget: { maxTokensPerCase: 16384 },
3775
+ childTask: {
3776
+ executor: "pi",
3777
+ role: "implementer",
3778
+ skills: BACKEND_TEST_SKILLS_BY_ROLE.implementer,
3779
+ toolProfile: "write",
3780
+ complexity: "HIGH",
3781
+ writePolicy: "exclusive",
3782
+ allowedPaths: ["testcase/test_{{item.stem}}.py"],
3783
+ forbiddenPaths: Array.from(new Set([
3784
+ ...forbidden,
3785
+ "testcase/md/**",
3786
+ "conftest.py",
3787
+ "pytest.ini",
3788
+ "pyproject.toml",
3789
+ "setup.cfg",
3790
+ ])),
3791
+ writeSet: ["testcase/test_{{item.stem}}.py"],
3792
+ writerOutcomePolicy: {
3793
+ type: "implementation-outcome-v1",
3794
+ requireChangedFiles: true,
3795
+ },
3796
+ retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
3797
+ outputContract: "Write exactly one pytest module file testcase/test_<stem>.py whose actual test function region contains the exact Case ID, preferably in the function name or docstring. testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
3798
+ subtaskPromptTemplate: [
3799
+ "Convert the single Markdown module testcase/md/{{item.stem}}.md into pytest using the shared helpers/factories already written by the plan node. After reading the module Markdown and the bounded pytest config/conftest, immediately use write tools to create the single file testcase/test_{{item.stem}}.py. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.",
3800
+ "Output budget protocol (hard, max output <=16K per turn): Write exactly one test_{{item.stem}}.py. 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.",
3801
+ "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.",
3802
+ 'Ensure every 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. 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 `pytest.param(..., id=...)` for every row; 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). 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 前置条件/测试数据/自动化映射.',
3803
+ "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 one `testcase/test_{{item.stem}}.py`. 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 module stem 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.",
3804
+ "Reuse the shared HTTP logging/redaction helper and request factories from testcase/**/helpers/** and testcase/**/factories/**; do not redefine them here. 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.",
3805
+ "Do not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`.",
3806
+ ].join("\n\n"),
3807
+ },
3808
+ },
3809
+ };
3810
+ const collectionAssess = shellNode("assess-backend-pytest-collection-shell", [generatePytestCasesMap.id], "markdown-collection-assess", "Resolve final Markdown-mapped scripts before any business test body execution. A safe deterministic mapped script that the pytest writer omitted is REPAIRABLE without starting pytest; otherwise run pytest collection only over existing mapped scripts. Materialize hash-bound PASS/REPAIRABLE/BLOCKED facts. Only missing mapped generated scripts and generated testcase-local syntax/import inconsistencies are repairable; dependency, plugin, production-module, environment, safety and unknown failures remain blocked.", "Run-owned reports/backend-test-pytest-collection-initial.md and contracts/backend-test-pytest-collection-initial.json with bounded diagnostics, asset hashes, collected item IDs and deterministic repair eligibility.", [], 120000);
3816
3811
  const repairPytest = {
3817
3812
  id: "repair-backend-pytest-collection-pi",
3818
3813
  depends_on: [collectionAssess.id],
@@ -3856,7 +3851,7 @@ async function buildBackendTestHybridDag(sources) {
3856
3851
  'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
3857
3852
  'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
3858
3853
  ].join("; ");
3859
- const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 4 Markdown validation + case coverage and node 9 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
3854
+ const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
3860
3855
  if (execute.shell) {
3861
3856
  execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
3862
3857
  }
@@ -3880,11 +3875,11 @@ async function buildBackendTestHybridDag(sources) {
3880
3875
  ? "Final Markdown report and L-5 conclusion under docs/test-reports/**; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked, not re-rendered; no JSON."
3881
3876
  : "Final Markdown report and L-5 conclusion in assistant output; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked; no JSON or writes.",
3882
3877
  subtask_prompt: [
3883
- "Generate the final Markdown report only from authoritative run-owned artifacts. Read node 1 reports/backend-test-environment.md; node 4 backend-md-case-validation.md and backend-test-case-coverage-analysis.md; nodes 6/8 backend-test-pytest-collection initial/effective reports and facts; node 9 backend-test-traceability.md, backend-test-markdown-pytest-correspondence.md and backend-test-scenario-param-consistency.md; node 10 contracts/backend-test-case-manifest.json; and node 11 backend-test-result.json, backend-test-facts.md, backend-test-failure-analysis.md, pytest-html/HTML and L-5 dashboard. Do not use node 2/3/5 assistant prose as facts. Do not emit JSON.",
3878
+ "Generate the final Markdown report only from authoritative run-owned artifacts. Read node 1 reports/backend-test-environment.md; node 6 backend-md-case-validation.md and backend-test-case-coverage-analysis.md; nodes 10/12 backend-test-pytest-collection initial/effective reports and facts; node 13 backend-test-traceability.md, backend-test-markdown-pytest-correspondence.md and backend-test-scenario-param-consistency.md; node 14 contracts/backend-test-case-manifest.json; and node 15 backend-test-result.json, backend-test-facts.md, backend-test-failure-analysis.md, pytest-html/HTML and L-5 dashboard. Do not use node 2/3/4/7/8/9 assistant prose as facts. Do not emit JSON.",
3884
3879
  "Use this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion. Prefer linking reports/backend-test-failure-analysis.md for structured failure analysis rather than inventing classifications.",
3885
3880
  "Output budget: list evidence paths first, then write a short fixed six-section report; never paste upstream full text into chat.",
3886
- "The L-5 metrics and visualization are produced deterministically by node 11 at reports/backend-test-l5-dashboard.html. Link to that dashboard as the authoritative L-5 view. Pytest execution facts come from node 11; coverage/correspondence numbers and materializationStatus come from node 10; collection authorization comes from nodes 6/8; detailed coverage findings come from node 4; detailed mapping findings come from node 9. Never recompute these values. If machine manifest and human reports disagree, report evidence inconsistency rather than silently choosing.",
3887
- "Always state the exact Coverage Scope classification, policy, affected operations, regression floor, completeness claim, PASS/FAIL/UNAVAILABLE status and findings from node 4 case validation + coverage, plus node 9 traceability + correspondence. Affected-scope or affected-operations-full coverage must never be described as whole-API completeness unless every operation is explicitly listed. Their FAIL status does not block pytest, but it must remain visible and must never be rewritten as PASS.",
3881
+ "The L-5 metrics and visualization are produced deterministically by node 15 at reports/backend-test-l5-dashboard.html. Link to that dashboard as the authoritative L-5 view. Pytest execution facts come from node 15; coverage/correspondence numbers and materializationStatus come from node 14; collection authorization comes from nodes 10/12; detailed coverage findings come from node 6; detailed mapping findings come from node 13. Never recompute these values. If machine manifest and human reports disagree, report evidence inconsistency rather than silently choosing.",
3882
+ "Always state the exact Coverage Scope classification, policy, affected operations, regression floor, completeness claim, PASS/FAIL/UNAVAILABLE status and findings from node 6 case validation + coverage, plus node 13 traceability + correspondence. Affected-scope or affected-operations-full coverage must never be described as whole-API completeness unless every operation is explicitly listed. Their FAIL status does not block pytest, but it must remain visible and must never be rewritten as PASS.",
3888
3883
  "Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY. Distinguish Markdown Case count, primary pytest symbol count, collected pytest item count, variant/assertion/cross-cutting Test Point counts and execution amplification; never describe pytest item count as the number of business scenarios.",
3889
3884
  "Never override Shell/pytest-html facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
3890
3885
  canWriteReport
@@ -3902,9 +3897,9 @@ async function buildBackendTestHybridDag(sources) {
3902
3897
  globalConstraints: [
3903
3898
  ...taskConfig.hardConstraints,
3904
3899
  ...STANDARD_GLOBAL_CONSTRAINTS,
3905
- "backend-test-dag uses exactly 12 real top-level tasks. Pytest collection runs once on the green path and at most twice only when one bounded pre-execution repair is eligible; business pytest test bodies execute exactly once over safe scripts explicitly mapped by final Markdown cases.",
3900
+ "backend-test-dag uses exactly 16 real top-level tasks: Markdown cases are sharded via a README plan + manifest shell + map_agent barrier (one child per module, each with its own 16K Pi budget), and pytest is likewise sharded via a shared-asset plan + manifest shell + map_agent barrier. Pytest collection runs once on the green path and at most twice only when one bounded pre-execution repair is eligible; business pytest test bodies execute exactly once over safe scripts explicitly mapped by final Markdown cases.",
3906
3901
  "Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
3907
- "Environment, advisory Markdown validation/coverage, collection initial/effective facts, advisory traceability/correspondence, pre-execution scenario-param consistency (with at most one deterministic param repair), canonical manifest, pytest-html, HTML, failure-analysis and execution facts are deterministic evidence. Node 4 quality findings stay advisory; nodes 6/8 form the fail-closed collection authorization; node 9 traceability/scenario-param findings stay advisory by default; node 10 partial/unavailable manifest does not block node 11 when effective collection remains fresh.",
3902
+ "Environment, advisory Markdown validation/coverage, collection initial/effective facts, advisory traceability/correspondence, pre-execution scenario-param consistency (with at most one deterministic param repair), canonical manifest, pytest-html, HTML, failure-analysis and execution facts are deterministic evidence. Node 6 quality findings stay advisory; nodes 10/12 form the fail-closed collection authorization; node 13 traceability/scenario-param findings stay advisory by default; node 14 partial/unavailable manifest does not block node 15 when effective collection remains fresh.",
3908
3903
  "Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
3909
3904
  "Functional case IDs use canonical BE-<MODULE>-<NNN> with exactly three digits and no alphabetic suffix. Every Test Point has exactly one variant/assertion/cross-cutting binding; only variant bindings create pytest parameter items. Production code/config, skip/xfail, execution-result repair and business pytest rerun are forbidden; pre-execution repairs are limited to one collection-proven generated-test asset repair and one scenario-param payload repair (deterministic preferred).",
3910
3905
  "Writer nodes must obey multi-file output-budget protocol under 16K max tokens: one file per write/edit, no chat dumps; Completeness Gate may trigger bounded incomplete-write-set recovery without lowering coverage quality.",
@@ -3917,10 +3912,14 @@ async function buildBackendTestHybridDag(sources) {
3917
3912
  executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
3918
3913
  tasks: [
3919
3914
  environment,
3920
- generateCases,
3915
+ generateMdPlan,
3916
+ materializeMdManifest,
3917
+ generateMdCasesMap,
3921
3918
  reviewCases,
3922
3919
  validateCases,
3923
- generatePytest,
3920
+ generatePytestPlan,
3921
+ materializePytestManifest,
3922
+ generatePytestCasesMap,
3924
3923
  collectionAssess,
3925
3924
  repairPytest,
3926
3925
  collectionEffective,