@tea-agent/loop-agent 0.35.0-beta.1 → 0.35.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.
@@ -30,7 +30,9 @@ async function readNodeText(runDir, nodeId) {
30
30
  if (record.status !== "FINISHED") {
31
31
  throw new Error(`frontend prewrite gate requires FINISHED node ${nodeId} (got ${String(record.status)})`);
32
32
  }
33
- const text = record.assistantText?.trim() || record.stdout?.trim() || "";
33
+ const text = [record.assistantText, record.stdout]
34
+ .filter((value) => Boolean(value?.trim()))
35
+ .join("\n");
34
36
  if (!text)
35
37
  throw new Error(`frontend prewrite gate empty output from ${nodeId}`);
36
38
  return text;
@@ -182,7 +184,24 @@ export async function runFrontendPrewriteGate(input) {
182
184
  const raw = JSON.parse(await readFile(artifact.path, "utf8"));
183
185
  const contract = frontendImplementationContractSchema.parse(raw);
184
186
  if (!input.config.allowedMockStrategies.includes(contract.mockApi.strategy)) {
185
- throw new Error(`frontend prewrite gate blocked mock strategy ${contract.mockApi.strategy}; allowed=${input.config.allowedMockStrategies.join(",")}`);
187
+ const quotedAllowed = input.config.allowedMockStrategies
188
+ .map((strategy) => JSON.stringify(strategy))
189
+ .join(", ");
190
+ const forcedNotNeeded = input.config.allowedMockStrategies.length === 1 &&
191
+ input.config.allowedMockStrategies[0] === "not-needed";
192
+ const reason = forcedNotNeeded
193
+ ? "the task is auto/not-required or lacks authorized deterministic Mock verification commands"
194
+ : `allowed strategies are [${quotedAllowed}]`;
195
+ throw new Error(`frontend contract mock strategy "${contract.mockApi.strategy}" is incompatible with the generated allowed strategies [${quotedAllowed}]; ${reason}. Regenerate the DAG or set frontendMock.policy=required with authorized Mock verification commands.`);
196
+ }
197
+ // Fail early instead of at the verification trace: a non-not-needed strategy
198
+ // can only be proven by executing the DAG's frozen Mock verification
199
+ // commands. If none were materialized at generation time the contract would
200
+ // fail "trace: selected Mock strategy requires successful Mock verification
201
+ // commands" after implementation, so block before any write is authorized.
202
+ if (contract.mockApi.strategy !== "not-needed" &&
203
+ (input.config.mockCommandLabels?.length ?? 0) === 0) {
204
+ throw new Error(`frontend prewrite gate blocked: contract selected Mock strategy ${contract.mockApi.strategy} but the DAG has no authorized Mock verification commands; add frontendMock.verifyCommands or a project mock script, or select not-needed`);
186
205
  }
187
206
  if (input.config.implementationWriteSet) {
188
207
  const writeSet = new Set(input.config.implementationWriteSet);
@@ -599,12 +599,15 @@ export function resolveFrontendMockMode(capability, taskConfig, hasApiDep) {
599
599
  if (!hasApiDep) {
600
600
  return "not-required";
601
601
  }
602
- // In auto mode, frontend Mock is optional. Only require Mock-backed strategy
603
- // assessment when a native project Mock capability is actually present.
604
- // Projects without confirmed Mock support continue through the normal
605
- // frontend writer and must preserve the real integration gap instead of
606
- // inventing a temporary Mock framework or blocking solely for missing Mock.
607
- return capability.status === "present" ? "required" : "not-required";
602
+ // In auto mode, frontend Mock is optional. Only enter Mock-backed "required"
603
+ // mode when the project has a confirmed native Mock capability AND
604
+ // deterministic Mock verification commands. Without executable verification
605
+ // commands a non-not-needed strategy could never be verified, so auto falls
606
+ // back to not-required; the prewrite gate then forces not-needed and the
607
+ // plan records the Real Integration Gap instead of inventing commands.
608
+ return capability.status === "present" && hasDeterministicMockVerification
609
+ ? "required"
610
+ : "not-required";
608
611
  }
609
612
  function mapTaskComplexity(complexity) {
610
613
  if (complexity === "small")
@@ -2008,8 +2011,9 @@ function buildFrontendMockVerifyNode(sources, implementId, readOnlyPaths, forbid
2008
2011
  },
2009
2012
  };
2010
2013
  }
2011
- function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, globalConstraints) {
2014
+ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, globalConstraints, blockedReason) {
2012
2015
  const { taskConfig } = sources;
2016
+ const reason = blockedReason ?? "Mock contract is blocked by deterministic generation-time Mock safety constraints.";
2013
2017
  const spec = {
2014
2018
  version: 3,
2015
2019
  title: `Frontend implementation DAG (BLOCKED Mock): ${taskConfig.title}`,
@@ -2039,10 +2043,10 @@ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, glo
2039
2043
  allowedPaths: readOnlyPaths,
2040
2044
  forbiddenPaths,
2041
2045
  outputContract: "Deterministic generation-time Mock blocker. Always exits nonzero and never reaches a writer.",
2042
- subtask_prompt: "Fail closed because required Mock verification entrypoints were not materialized. Resolve the task contract and regenerate the DAG.",
2046
+ subtask_prompt: `Fail closed: ${reason} Resolve the task contract and regenerate the DAG.`,
2043
2047
  shell: {
2044
2048
  commands: [
2045
- "node -e \"console.error('frontend Mock contract blocked: required verification entrypoints are unavailable'); process.exit(1)\"",
2049
+ `node -e ${JSON.stringify(`console.error(${JSON.stringify(`frontend Mock contract blocked: ${reason}`)}); process.exit(1)`)}`,
2046
2050
  ],
2047
2051
  cwd: ".",
2048
2052
  timeoutMs: 60000,
@@ -2093,7 +2097,7 @@ function resolveFrontendMockContextBlock(sources) {
2093
2097
  if (mode === "not-required") {
2094
2098
  parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
2095
2099
  if (frontendMockStrategyMustBeNotNeeded(sources)) {
2096
- parts.push('Auto mode has no confirmed project Mock capability. The structured contract must set mockApi.strategy to "not-needed". Do not add Mock files or dependencies; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.');
2100
+ parts.push('Auto mode has no confirmed project Mock capability or no deterministic Mock verification command. The structured contract must set mockApi.strategy to "not-needed". Do not add Mock files or dependencies; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.');
2097
2101
  }
2098
2102
  }
2099
2103
  if (mode === "blocked") {
@@ -2102,10 +2106,15 @@ function resolveFrontendMockContextBlock(sources) {
2102
2106
  return parts.join("\n");
2103
2107
  }
2104
2108
  function frontendMockStrategyMustBeNotNeeded(sources) {
2105
- const capabilityStatus = sources.frontendMockCapability?.status;
2106
- return ((sources.taskConfig.frontendMock?.policy ?? "auto") === "auto" &&
2109
+ const policy = sources.taskConfig.frontendMock?.policy ?? "auto";
2110
+ const capability = sources.frontendMockCapability;
2111
+ const capabilityStatus = capability?.status;
2112
+ const hasDeterministicMockVerification = (capability?.verifyCommands.length ?? 0) > 0;
2113
+ return (policy === "auto" &&
2107
2114
  (sources.frontendMockMode ?? "not-required") === "not-required" &&
2108
- (capabilityStatus === "absent" || capabilityStatus === "ambiguous"));
2115
+ (capabilityStatus === "absent" ||
2116
+ capabilityStatus === "ambiguous" ||
2117
+ !hasDeterministicMockVerification));
2109
2118
  }
2110
2119
  function resolveFrontendCapabilityContextBlock(sources) {
2111
2120
  const risk = sources.frontendRisk;
@@ -2355,7 +2364,12 @@ async function buildFrontendHybridDagFromTask(sources) {
2355
2364
  ];
2356
2365
  // Guard: blocked mode — generate assessment-only DAG with no writer reachable
2357
2366
  if (mockMode === "blocked") {
2358
- return buildBlockedFrontendMockDag(frontendSources, readOnlyPaths, forbiddenPaths, globalConstraints);
2367
+ const blockedReason = mockCapability.safetyViolation
2368
+ ? `Mock contract blocked: ${mockCapability.safetyViolation}`
2369
+ : (taskConfig.frontendMock?.policy ?? "auto") === "required"
2370
+ ? "Mock strategy is required, but no authorized Mock verification command was found."
2371
+ : "Mock contract is blocked by deterministic generation-time Mock safety constraints.";
2372
+ return buildBlockedFrontendMockDag(frontendSources, readOnlyPaths, forbiddenPaths, globalConstraints, blockedReason);
2359
2373
  }
2360
2374
  const fallbackVerifyCommands = await discoverFrontendFallbackVerifyCommands(sources.repoRoot);
2361
2375
  const staticFallbackCommands = fallbackVerifyCommands.staticCommands;
@@ -2521,14 +2535,14 @@ async function buildFrontendHybridDagFromTask(sources) {
2521
2535
  allowedPaths: readOnlyPaths,
2522
2536
  forbiddenPaths,
2523
2537
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2524
- outputContract: "Markdown implementation plan with Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks, followed by exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1 when this node is the effective plan source. Do NOT include multiple fenced JSON blocks; only the single authoritative contract JSON block is accepted. No file writes.",
2538
+ outputContract: "Markdown implementation plan with Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks, ending with exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1. This fenced block is the single authoritative implementation contract the prewrite gate materializes; it must appear exactly once and must not contain or be followed by any raw JSON or extra fenced block. No file writes.",
2525
2539
  subtask_prompt: [
2526
2540
  "Based on frontend-contract-pi, frontend-scout-pi, task sources, and the generation-time Mock capability evidence, return a minimal frontend implementation plan.",
2527
2541
  "Select the Mock / API strategy inside the plan and structured contract. Carry endpoint/fixture mapping, explicit activation, production-default-off rule, verification commands, and Real Integration Gap into both outputs.",
2528
2542
  "Include ordered steps, target files, UI state handling, styling/component strategy, interaction notes, Mock/API strategy, dependency policy, deterministic verification entrypoints, and residual risks. Use only the fixed entrypoints below; implementation may add tests behind them but cannot replace them.",
2529
2543
  "Every target file and verification target must be selected from the current target workspace and task scope. Do not reuse paths or symbols from examples, prior tasks, or loop-agent itself; if the project uses app/, packages/, spec/, __tests__, or another layout, preserve that layout.",
2530
2544
  "Consume the Scout TARGET_SURFACE evidence before selecting files. Preserve the discovered existing entrypoint and data source. If implementationPaths or testPaths are outside task allowedPaths, record a blocking scope conflict; do not substitute a new page or silently broaden the writeSet.",
2531
- "End with exactly one fenced json object conforming to frontend-implementation-contract-v1 so small topology can materialize the contract without plan-revision.",
2545
+ "End with exactly one fenced json object conforming to frontend-implementation-contract-v1. This node is the single contract JSON producer: the fenced block is the authoritative contract the prewrite gate materializes. Do not emit any raw JSON, JSON in prose, or a second fenced block anywhere in the response; the plan text must not contain other balanced JSON objects.",
2532
2546
  "Each requirement must state its user-observable or logic-observable expectedOutcome. Each interaction must state its trigger and expectedBehavior. IDs plus file paths are not sufficient behavior semantics.",
2533
2547
  requirementCoverageInstruction,
2534
2548
  "Read-only: do not modify code, docs, artifacts, or repository files.",
@@ -2548,6 +2562,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2548
2562
  allowedPaths: readOnlyPaths,
2549
2563
  forbiddenPaths,
2550
2564
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
2565
+ outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
2551
2566
  outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Required Plan Corrections, and Checked Items. No file writes.",
2552
2567
  subtask_prompt: [
2553
2568
  "Audit the frontend plan before implementation.",
@@ -2572,7 +2587,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2572
2587
  allowedPaths: readOnlyPaths,
2573
2588
  forbiddenPaths,
2574
2589
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2575
- outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1. Do NOT include multiple fenced JSON blocks; only the single authoritative contract JSON block is accepted. The JSON is the authoritative materialization input. No file writes.",
2590
+ outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1. This fenced block is the single authoritative implementation contract the prewrite gate materializes; it must appear exactly once and must not contain or be followed by any raw JSON or extra fenced block. Do NOT include multiple fenced JSON blocks; only the single authoritative contract JSON block is accepted. No file writes.",
2576
2591
  subtask_prompt: [
2577
2592
  "Consume frontend-plan-pi (original plan) and frontend-design-review-pi (first design review findings).",
2578
2593
  "This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
@@ -2580,7 +2595,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2580
2595
  requirementCoverageInstruction,
2581
2596
  "Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
2582
2597
  "Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
2583
- "End the response with exactly one fenced json object conforming to frontend-implementation-contract-v1. Bind it to the supplied task sources; map every requirement and applicable UI state to concrete implementation and verification targets or an explicit blocking evidence gap. Do not include secrets or unsafe paths.",
2598
+ "End the response with exactly one fenced json object conforming to frontend-implementation-contract-v1. This node is the single contract JSON producer when the design review requests revision: the fenced block is the authoritative contract the prewrite gate materializes. Do not emit any raw JSON, JSON in prose, or a second fenced block anywhere in the response. Bind it to the supplied task sources; map every requirement and applicable UI state to concrete implementation and verification targets or an explicit blocking evidence gap. Do not include secrets or unsafe paths.",
2584
2599
  "Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the revised contract; do not reduce behavior semantics to IDs and paths.",
2585
2600
  "verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
2586
2601
  fixedVerificationContext,
@@ -2604,6 +2619,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2604
2619
  allowedPaths: readOnlyPaths,
2605
2620
  forbiddenPaths,
2606
2621
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
2622
+ outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
2607
2623
  outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
2608
2624
  subtask_prompt: [
2609
2625
  "Audit the revised frontend plan before implementation. This node runs only after request-revision and consumes frontend-plan-revision-pi.",
@@ -2617,66 +2633,9 @@ async function buildFrontendHybridDagFromTask(sources) {
2617
2633
  sourceContext,
2618
2634
  ].join("\n\n"),
2619
2635
  },
2620
- {
2621
- id: "frontend-contract-json-pi",
2622
- depends_on: [
2623
- "frontend-plan-revision-pi",
2624
- "frontend-plan-pi",
2625
- "frontend-final-design-review-pi",
2626
- "frontend-design-review-pi",
2627
- ],
2628
- dependsPolicy: "all-or-condition-skip",
2629
- role: "planner",
2630
- executor: "pi",
2631
- complexity: "MED",
2632
- writePolicy: "read-only",
2633
- outputMode: "structured-required",
2634
- retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
2635
- allowedPaths: readOnlyPaths,
2636
- forbiddenPaths,
2637
- skills: FRONTEND_IMPLEMENTATION_SKILLS,
2638
- outputContract: "Return exactly one raw JSON object conforming to frontend-implementation-contract-v1. OUTPUT ONLY THE JSON OBJECT. NO MARKDOWN, NO PROSE, NO COMMENTS, NO CODE FENCES, NO BACKTICKS. The first character must be '{' and the last character must be '}'. Any text before or after the JSON will cause the output to be REJECTED.",
2639
- subtask_prompt: [
2640
- "Convert the effective reviewed frontend plan into the canonical frontend-implementation-contract-v1 JSON.",
2641
- "Use frontend-plan-revision-pi when it is FINISHED; otherwise use frontend-plan-pi. Confirm the effective design review passed before producing the contract.",
2642
- "Return only the JSON object. Do not wrap it in Markdown or a code fence. Do not add explanatory text.",
2643
- "Preserve all requirement expectedOutcome, interaction trigger/expectedBehavior, target files, verification targets, Mock/API decisions, and Real Integration Gap from the effective plan.",
2644
- "OUTPUT REQUIREMENT: The response must consist solely of a raw JSON object - no Markdown headers, no code fences (no \`\`\`json), no explanatory prose before or after. The very first character you output must be '{' and the very last character must be '}'. If you add ANY text, the extraction gate will reject the output.",
2645
- "verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
2646
- requirementCoverageInstruction,
2647
- fixedVerificationContext,
2648
- frontendContractSchemaBlock,
2649
- sourceContext,
2650
- ].join("\n\n"),
2651
- },
2652
- {
2653
- id: "frontend-contract-json-validate-shell",
2654
- depends_on: ["frontend-contract-json-pi"],
2655
- role: "verifier",
2656
- executor: "shell",
2657
- complexity: "LOW",
2658
- writePolicy: "read-only",
2659
- allowedPaths: readOnlyPaths,
2660
- forbiddenPaths,
2661
- outputContract: "Validated frontend implementation contract artifact with schema ID and SHA-256.",
2662
- subtask_prompt: "Materialize and validate the structured frontend contract before prewrite authorization.",
2663
- shell: {
2664
- commands: [],
2665
- jsonArtifactGate: {
2666
- fromNodeId: "frontend-contract-json-pi",
2667
- schemaId: "frontend-implementation-contract-v1",
2668
- artifactName: "frontend-implementation-contract.json",
2669
- outputDir: "contracts",
2670
- },
2671
- cwd: ".",
2672
- timeoutMs: 60000,
2673
- },
2674
- },
2675
2636
  {
2676
2637
  id: "frontend-prewrite-gate-shell",
2677
2638
  depends_on: [
2678
- "frontend-contract-json-pi",
2679
- "frontend-contract-json-validate-shell",
2680
2639
  "frontend-final-design-review-pi",
2681
2640
  "frontend-design-review-pi",
2682
2641
  "frontend-plan-revision-pi",
@@ -2695,14 +2654,12 @@ async function buildFrontendHybridDagFromTask(sources) {
2695
2654
  commands: [],
2696
2655
  frontendPrewriteGate: {
2697
2656
  schemaVersion: 1,
2698
- planFromNodeId: "frontend-contract-json-pi",
2699
- planFallbackFromNodeIds: [
2700
- "frontend-plan-revision-pi",
2701
- "frontend-plan-pi",
2702
- ],
2657
+ planFromNodeId: "frontend-plan-revision-pi",
2658
+ planFallbackFromNodeIds: ["frontend-plan-pi"],
2703
2659
  reviewFromNodeId: "frontend-final-design-review-pi",
2704
2660
  reviewFallbackFromNodeIds: ["frontend-design-review-pi"],
2705
2661
  requiredRequirementIds: requirementIds,
2662
+ mockCommandLabels: mockVerifyEvidence?.commandLabels ?? [],
2706
2663
  allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
2707
2664
  frontendMockStrategyMustBeNotNeeded(frontendSources)
2708
2665
  ? ["not-needed"]
@@ -183,6 +183,8 @@ export const dagFrontendPrewriteGateSchema = z.object({
183
183
  allowedMockStrategies: z
184
184
  .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
185
185
  .min(1),
186
+ /** Frozen Mock verification command labels the DAG will actually execute. */
187
+ mockCommandLabels: z.array(z.string()).default([]),
186
188
  artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
187
189
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
188
190
  requireSourceFreshness: z.literal(true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.35.0-beta.1",
3
+ "version": "0.35.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -6,8 +6,10 @@ Pre-write nodes are read-only. Preserve IDs, labels, commands, language, require
6
6
 
7
7
  - **`frontend-contract-pi`**: `Scope`, `Non-goals`, `Acceptance Criteria`, `UI States`, `Target Runtime Environment`, `Risks`, `Verification Expectations`. No guessed requirements.
8
8
  - **`frontend-scout-pi`**: routes, components, tokens, data/API/Mock, scripts, tests, assets. Fact vs inference vs gap. Query the knowledge base when available and always search+read `openspec/schemas/`, `openspec/project-specs/`, and `ai_workspace/` before repo fallback. Output stack, routes, components, styling, conventions, state/data, test entry points, reuse, risks.
9
- - **`frontend-plan-pi` + conditional design loop**: AC → explicit observable `expectedOutcome`, interactions → explicit `trigger` + `expectedBehavior`, then steps, in-bound files, applicable UI states, reuse, deps, Mock/API strategy, activation/rollback, frozen verify entrypoints, real-integration gap, and exactly one `frontend-implementation-contract-v1` JSON object. IDs plus file paths are not sufficient behavior semantics. Use `uiStates: []` for logic-only changes with no user-visible UI state; do not invent UI states. Applicable states require behavior/implementation/verification, while non-applicable states require a reason and omit empty behavior placeholders. Prefer native Mock; browser intercept only with existing e2e; request-adapter only for a reversible seam. `auto` with absent/ambiguous project Mock capability must select `not-needed`, keep real requests default, and record the gap; `required` cannot select `not-needed`. Initial design pass uses the original plan; only exact `request-revision` runs read-only revision plus final review. Small-risk runs one design review only.
10
- - **`frontend-prewrite-gate-shell`**: the sole write authorization. Resolve effective plan/review, require exact pass, retain every REQ/BR/AC id, enforce Mock policy, validate schema/source binding and writeSet containment, and materialize `contracts/frontend-implementation-contract.json`. A planned fixture or consumer may be a future writer output and need not exist before authorization. Fallback is allowed only when a conditional primary is absent; an existing malformed primary fails closed. Generation-time blocked Mock produces one deterministic blocking shell node and no writer.
9
+ - **`frontend-plan-pi` + conditional design loop**: AC → explicit observable `expectedOutcome`, interactions → explicit `trigger` + `expectedBehavior`, then steps, in-bound files, applicable UI states, reuse, deps, Mock/API strategy, activation/rollback, frozen verify entrypoints, and real-integration gap. These nodes output Markdown plans only; each plan ends with **exactly one** fenced `json` block carrying the single authoritative `frontend-implementation-contract-v1` object. Do not emit raw JSON, JSON in prose, or a second fenced block. `frontend-contract-json-pi` and `frontend-contract-json-validate-shell` do not exist; the prewrite gate materializes the contract from the effective plan node. IDs plus file paths are not sufficient behavior semantics. Use `uiStates: []` for logic-only changes with no user-visible UI state; do not invent UI states. Applicable states require behavior/implementation/verification, while non-applicable states require a reason and omit empty behavior placeholders. Prefer native Mock; browser intercept only with existing e2e; request-adapter only for a reversible seam. A non-`not-needed` strategy requires frozen Mock verify commands (`frontendMock.verifyCommands` / `package.json` mock script / capability seed); `auto` with absent/ambiguous capability or no command selects `not-needed` (real requests stay default, gap recorded); `required` without a command is generation-time blocked (no writer is generated). Initial design pass uses the original plan; only exact `request-revision` runs read-only revision plus final review. Small-risk runs one design review only.
10
+ - **`frontend-prewrite-gate-shell`**: the sole write authorization. Resolve the effective plan (revised plan when the revision branch ran, otherwise the original plan) and its review, require exact pass, retain every REQ/BR/AC id, enforce Mock policy, validate schema/source binding and writeSet containment, and materialize `contracts/frontend-implementation-contract.json` from the **single** fenced contract block in the effective plan node (multiple candidates fail closed with `invalid-output`). A planned fixture or consumer may be a future writer output and need not exist before authorization. Fallback is allowed only when a conditional primary is absent; an existing malformed primary fails closed. Generation-time blocked Mock produces one deterministic blocking shell node and no writer.
11
+ - Every `verificationTarget.commandLabel` must be one of the **frozen command labels** derived from the DAG run spec's `verifyEvidence.commandLabels`; any other value is rejected fail-closed at materialization (`invalid-output`). An empty frozen set (e.g. no `run.json`) skips the check.
12
+ - `mockApi.strategy !== "not-needed"` fails closed at the gate when `mockCommandLabels` is empty (`no authorized Mock verification commands`).
11
13
  - **`frontend-implement-pi`**: sole regular exclusive writer and consumer of `frontend-bounded-implement`, not this discovery skill. Stay in `writeSet`; real requests default-on; Mock reversible, dev/test-only, production-off. Atomic handler/intercept/adapter with consumer+tests. Stop on forbidden paths or guesses. First line must be `IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked`; runtime checks it against the attributed diff. Optional mock-verify when frozen; static+behavior always; behavior must prove page consumption. Skipped-Mock `not-needed` keeps real integration pending unless the real backend path has fresh evidence.
12
14
 
13
15
  ## Contract / trace / stages (M1–M2)