@skyramp/mcp 0.4.1-rc.1 → 0.4.1

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.
Files changed (67) hide show
  1. package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.js +2 -1
  2. package/build/prompts/enhance-assertions/integrationAssertionsPrompt.js +2 -1
  3. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -1
  4. package/build/prompts/enhance-assertions/sharedAssertionRules.js +41 -22
  5. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +17 -9
  6. package/build/prompts/test-recommendation/diffExecutionPlan.js +0 -2
  7. package/build/prompts/test-recommendation/test-recommendation-prompt.js +8 -2
  8. package/build/prompts/testbot/testbot-prompts.js +12 -7
  9. package/build/recommendation/registerPlan.d.ts +5 -1
  10. package/build/recommendation/registerPlan.js +5 -0
  11. package/build/recommendation/types.d.ts +25 -2
  12. package/build/recommendation/verifierContracts.d.ts +16 -4
  13. package/build/recommendation/verifierContracts.js +20 -4
  14. package/build/recommendation/verifiers/coverage.d.ts +10 -0
  15. package/build/recommendation/verifiers/coverage.js +144 -22
  16. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +22 -0
  17. package/build/recommendation/verifiers/deliveredMatchesPlan.js +43 -0
  18. package/build/recommendation/verifiers/existingCoverage.js +53 -0
  19. package/build/recommendation/verifiers/expectedValueSourced.js +111 -14
  20. package/build/services/TestGenerationService.js +3 -1
  21. package/build/tools/code-refactor/codeReuseTool.js +1 -1
  22. package/build/tools/code-refactor/reuse-outcome.d.ts +1 -1
  23. package/build/tools/code-refactor/reuse-state.d.ts +85 -7
  24. package/build/tools/code-refactor/reuse-state.js +239 -34
  25. package/build/tools/code-refactor/utils-verify-gates.d.ts +5 -0
  26. package/build/tools/code-refactor/utils-verify-gates.js +103 -11
  27. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +2 -1
  28. package/build/tools/submitReportTool.js +259 -38
  29. package/build/tools/test-management/actionsTool.js +5 -0
  30. package/build/tools/test-management/analyzeChangesTool.d.ts +53 -0
  31. package/build/tools/test-management/analyzeChangesTool.js +55 -2
  32. package/build/tools/test-management/registerTestPlanTool.d.ts +2 -1
  33. package/build/tools/test-management/registerTestPlanTool.js +29 -14
  34. package/build/types/ReuseOutcome.d.ts +73 -7
  35. package/build/types/TestAnalysis.d.ts +6 -0
  36. package/build/types/TestbotReport.d.ts +15 -1
  37. package/build/utils/AnalysisStateManager.d.ts +7 -1
  38. package/build/utils/AnalysisStateManager.js +5 -1
  39. package/build/utils/assertion-verify/api-shared-lints.js +71 -34
  40. package/build/utils/assertion-verify/format.js +2 -2
  41. package/build/utils/assertion-verify/helper-imports.d.ts +7 -0
  42. package/build/utils/assertion-verify/helper-imports.js +119 -27
  43. package/build/utils/assertion-verify/lint-types.d.ts +31 -2
  44. package/build/utils/assertion-verify/lint-types.js +66 -0
  45. package/build/utils/assertion-verify/metrics.d.ts +13 -0
  46. package/build/utils/assertion-verify/metrics.js +16 -0
  47. package/build/utils/assertion-verify/verify.d.ts +11 -6
  48. package/build/utils/assertion-verify/verify.js +56 -15
  49. package/build/utils/canonicalJson.d.ts +11 -0
  50. package/build/utils/canonicalJson.js +17 -0
  51. package/build/utils/utils-verify/action-key.d.ts +27 -0
  52. package/build/utils/utils-verify/action-key.js +292 -0
  53. package/build/utils/utils-verify/allow.d.ts +8 -1
  54. package/build/utils/utils-verify/allow.js +14 -1
  55. package/build/utils/utils-verify/call-sites.d.ts +76 -8
  56. package/build/utils/utils-verify/call-sites.js +256 -70
  57. package/build/utils/utils-verify/language-spec.d.ts +3 -2
  58. package/build/utils/utils-verify/parse.d.ts +22 -3
  59. package/build/utils/utils-verify/parse.js +123 -52
  60. package/build/utils/utils-verify/verify.d.ts +33 -3
  61. package/build/utils/utils-verify/verify.js +126 -12
  62. package/build/utils/workspaceAuth.d.ts +59 -19
  63. package/build/utils/workspaceAuth.js +228 -31
  64. package/package.json +1 -1
  65. package/plugin/prompts/generate-tests/execution-plan.md +1 -1
  66. package/plugin/prompts/generate-tests/generation.md +1 -0
  67. package/plugin/prompts/plan-tests.md +33 -16
@@ -2,6 +2,8 @@ import { allChangedFiles } from "../types.js";
2
2
  import { changedPathSet, normalizeCitedPath } from "./citedPath.js";
3
3
  import { EXPECTED_VALUE_SOURCED_CONTRACT as CONTRACT } from "../verifierContracts.js";
4
4
  import { appearsIn, searchable } from "../pullRequestText.js";
5
+ import { escapeRegExp } from "../../utils/regex.js";
6
+ import { renderCase } from "./coverage.js";
5
7
  function readSource(raw) {
6
8
  const source = typeof raw === "string" ? raw.trim() : "";
7
9
  if (source.length === 0)
@@ -28,6 +30,71 @@ function readSource(raw) {
28
30
  function caseLabel(change, entry) {
29
31
  return `${String(change?.id ?? "?")}:${String(entry?.param ?? "?")}`;
30
32
  }
33
+ /** Every number the pull request writes, as a number. One value has many
34
+ * spellings — `1e21`, `1e+21`, `1E21`, the digits in full — and matching the
35
+ * value's OWN spelling refused every other one, so a pull request stating the
36
+ * limit as `1e21` did not state `1e21`.
37
+ *
38
+ * A token glued to a word or to another number is not a number the text states:
39
+ * that is what keeps `3600` from stating 60, `159.9989` from stating 159.998, and
40
+ * the `60` of `1e60` from being a number at all. The sign belongs to the token, so
41
+ * `-60` states -60 and not 60, while `+60` states 60. `Number` reads the rest:
42
+ * `1599.80` is 1599.8 and `10.0` is 10. */
43
+ function statedNumbers(prText) {
44
+ const stated = [];
45
+ for (const match of prText.matchAll(/[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g)) {
46
+ const token = match[0];
47
+ const start = match.index ?? 0;
48
+ const end = start + token.length;
49
+ const before = prText[start - 1] ?? "";
50
+ const after = prText[end] ?? "";
51
+ if (/[\dA-Za-z.]/.test(before))
52
+ continue;
53
+ if (/[\dA-Za-z]/.test(after))
54
+ continue;
55
+ // A dot that carries more digits makes this the first part of something
56
+ // longer — a version, a list — rather than a number of its own.
57
+ if (after === "." && /\d/.test(prText[end + 1] ?? ""))
58
+ continue;
59
+ const parsed = Number(token);
60
+ if (Number.isFinite(parsed))
61
+ stated.push(parsed);
62
+ }
63
+ return stated;
64
+ }
65
+ /** Whether the pull request writes this word or phrase, on its own rather than
66
+ * inside a longer word. A plain substring let `active` read itself out of
67
+ * `inactive`. The boundary is a letter or a digit, so a phrase that ends in
68
+ * punctuation still matches the sentence that carries it. */
69
+ function statesTheString(prText, value) {
70
+ const folded = value.replace(/\s+/g, " ").trim().toLowerCase();
71
+ if (!folded)
72
+ return false;
73
+ return new RegExp(`(?<![\\p{L}\\p{N}])${escapeRegExp(folded)}(?![\\p{L}\\p{N}])`, "u").test(prText);
74
+ }
75
+ /** Whether the pull request writes this value, anywhere in the title or the
76
+ * description. A string or a literal — `true`, `false`, `null` — has to appear as
77
+ * a word of its own; a number is compared against the numbers the text writes,
78
+ * whatever spelling either side uses.
79
+ *
80
+ * TRUE for a value that is not there to state: a case whose expectation is only a
81
+ * `derived` rule declares no value, and the rule is checked through the change's
82
+ * quote instead. */
83
+ function statesTheValue(prText, value) {
84
+ if (typeof value === "string")
85
+ return statesTheString(prText, value);
86
+ // Three values with no spelling of their own. An empty spelling list used to read
87
+ // as "stated", so a case could claim `true` from a pull request that writes the
88
+ // opposite.
89
+ if (value === true || value === false || value === null)
90
+ return statesTheString(prText, String(value));
91
+ // Nothing to state: `undefined` is the derived-only case, and a non-finite
92
+ // number has no spelling a pull request could carry.
93
+ if (typeof value !== "number" || !Number.isFinite(value))
94
+ return true;
95
+ return statedNumbers(prText).includes(value);
96
+ }
97
+ const text = (value) => (typeof value === "string" ? value.trim() : "");
31
98
  /** Verifier 10. A case that states what the response must carry says where that
32
99
  * value came from, and the value did not come from the code under test.
33
100
  *
@@ -52,6 +119,7 @@ export const expectedValueSourced = {
52
119
  const changed = changedPathSet(allChangedFiles(ctx), true);
53
120
  const readable = typeof ctx?.citedFileExists === "function" ? ctx.citedFileExists : undefined;
54
121
  const prText = searchable(ctx?.pullRequest ?? { title: "", description: "" });
122
+ const raised = new Set();
55
123
  const changes = Array.isArray(registration.changes) ? registration.changes : [];
56
124
  for (const change of changes) {
57
125
  const changeId = String(change?.id ?? "?");
@@ -60,7 +128,11 @@ export const expectedValueSourced = {
60
128
  // `undefined` is "this case states no expected value"; `null` is a stated
61
129
  // expectation that the field comes back null, which needs a source like
62
130
  // any other value.
63
- if (!entry || entry.expectedValue === undefined)
131
+ // A case states an expectation either as a value or as the rule behind one,
132
+ // and BOTH are read from somewhere: crediting `derived` without reading its
133
+ // `expectedFrom` let a rule "read" off the running app satisfy the check
134
+ // this verifier exists to be.
135
+ if (!entry || (entry.expectedValue === undefined && text(entry.derived).length === 0))
64
136
  return;
65
137
  const label = caseLabel(change, entry);
66
138
  const where = `${label} (source "${String(entry.expectedFrom ?? "")}")`;
@@ -69,27 +141,45 @@ export const expectedValueSourced = {
69
141
  // answer that addressed two of them and closed, and the id an answer is
70
142
  // carried forward by must therefore name the case as well as the change.
71
143
  const param = String(entry?.param ?? "").trim() || `case-${index + 1}`;
72
- const raise = (kind, objection, evidence) => objections.push({
73
- objectionId: `expectedValueSourced:${kind}:${changeId}:${param}`,
74
- verifier: "expectedValueSourced",
75
- message: objection.message,
76
- evidence,
77
- suggestion: objection.suggestion,
78
- });
144
+ // The CASE, not its parameter. Two bound cases on one parameter each invent
145
+ // their own value, and keying on the parameter published one of them and
146
+ // carried its answer to the other. The rendering is the case's content, so
147
+ // it is the same string whatever order the cases arrive in — the identity
148
+ // `coverage:cases` already uses.
149
+ const caseId = renderCase({ param, value: entry.value, absent: entry.absent, expect: String(entry.expect ?? "") });
150
+ const raise = (kind, objection, evidence) => {
151
+ if (raised.has(`${kind}:${changeId}:${caseId}`))
152
+ return;
153
+ raised.add(`${kind}:${changeId}:${caseId}`);
154
+ objections.push({
155
+ objectionId: `expectedValueSourced:${kind}:${changeId}:${caseId}`,
156
+ verifier: "expectedValueSourced",
157
+ message: objection.message,
158
+ evidence,
159
+ suggestion: objection.suggestion,
160
+ });
161
+ };
79
162
  if (source.kind === "none") {
80
163
  raise("unsourced", CONTRACT.objections.missingSource, `expected value with no usable \`expectedFrom\`: ${where}`);
81
164
  }
82
165
  else if (source.kind === "code") {
83
- raise("code", CONTRACT.objections.sourcedFromCode, `expected value read from the code under test: ${label} (expected ${JSON.stringify(entry.expectedValue)})`);
166
+ raise("code", CONTRACT.objections.sourcedFromCode,
167
+ // A derived-only case pins no value, so quoting one read `expected
168
+ // undefined` — a sentence naming nothing the agent can act on.
169
+ `expected value read from the code under test: ${label} (${entry.expectedValue === undefined
170
+ ? `derived as "${text(entry.derived)}"`
171
+ : `expected ${JSON.stringify(entry.expectedValue)}`})`);
84
172
  }
85
173
  else if (source.kind === "unresolvable") {
86
174
  // A path no repository file can answer, so the citation opens nothing.
87
175
  raise("unreadable", CONTRACT.objections.unreadableSource, `source file not in the checkout: ${where}`);
88
176
  }
89
177
  else if (source.kind === "pullRequest") {
90
- // The RULE is what the pull request states; the value is derived from it.
91
- // "over 60 is rejected" states no 61, so looking for the value itself
92
- // objected to five correct cases in run 34414643135.
178
+ // Two readings, and the quote settles which one applies. A rule the pull
179
+ // request states decides values it never writes — "over 60 is rejected"
180
+ // states no 61, and demanding the value itself objected to five correct
181
+ // cases in run 34414643135 — so a case whose source states a rule puts
182
+ // that rule in `derived` and declares no number.
93
183
  const quote = String(change?.quote ?? "").trim();
94
184
  if (quote.length === 0) {
95
185
  raise("noQuote", CONTRACT.objections.quoteMissing, `${label} reads its value from the pull request; change ${changeId} states no \`quote\``);
@@ -97,6 +187,11 @@ export const expectedValueSourced = {
97
187
  else if (!appearsIn(prText, quote)) {
98
188
  raise("noQuote", CONTRACT.objections.quoteMissing, `${label} reads its value from the pull request; change ${changeId} quotes \`${quote}\`, which is in neither the title nor the description`);
99
189
  }
190
+ else if (!statesTheValue(prText, entry.expectedValue)) {
191
+ // `derived` is the case saying it pins NO number, so a case that still
192
+ // carries one is not excused by it: the number is what has to be stated.
193
+ raise("valueNotQuoted", CONTRACT.objections.valueNotQuoted, `${label} reads ${JSON.stringify(entry.expectedValue)} from the pull request, which writes no such value${text(entry.derived) ? " — and `derived` does not stand in for a value the case still declares" : ""}`);
194
+ }
100
195
  }
101
196
  else if (!source.file) {
102
197
  // A kind that names no file and is not the pull request has nothing to check.
@@ -129,7 +224,9 @@ export const expectedValueSourced = {
129
224
  const cited = changes.filter((change) => citedIds.includes(String(change?.id ?? "").trim()));
130
225
  if (cited.length === 0)
131
226
  continue;
132
- const statesAValue = cited.some((change) => (Array.isArray(change?.cases) ? change.cases : []).some((entry) => entry && entry.expectedValue !== undefined));
227
+ // `derived` counts: it states the rule the value follows from, which is a
228
+ // stated value in every sense except that no number is written down.
229
+ const statesAValue = cited.some((change) => (Array.isArray(change?.cases) ? change.cases : []).some((entry) => entry && (entry.expectedValue !== undefined || text(entry.derived).length > 0)));
133
230
  if (statesAValue)
134
231
  continue;
135
232
  const plannedTestId = String(plannedTest?.plannedTestId ?? "").trim();
@@ -140,7 +237,7 @@ export const expectedValueSourced = {
140
237
  message: CONTRACT.objections.failWithoutValue.message,
141
238
  evidence: `planned test ${plannedTestId || "(unnamed)"} expects to fail and cites ${cited
142
239
  .map((change) => String(change?.id ?? "?"))
143
- .join(", ")}; no case on those changes states an \`expectedValue\``,
240
+ .join(", ")}; no case on those changes states an \`expectedValue\` or a \`derived\` rule`,
144
241
  suggestion: CONTRACT.objections.failWithoutValue.suggestion,
145
242
  });
146
243
  }
@@ -438,7 +438,9 @@ The generated test file remains unchanged and ready to use as-is.
438
438
  if (generateOptions.authHeader === undefined) {
439
439
  try {
440
440
  const repoPath = generateOptions.outputDir || process.cwd();
441
- const resolved = await resolveAuthFromWorkspace(repoPath, generateOptions.authHeader, generateOptions.authScheme);
441
+ const resolved = await resolveAuthFromWorkspace(repoPath, generateOptions.authHeader, generateOptions.authScheme,
442
+ // The directory the test is written into names the service it is for.
443
+ generateOptions.outputDir);
442
444
  if (resolved) {
443
445
  generateOptions.authHeader = resolved.authHeader;
444
446
  if (resolved.authScheme !== undefined) {
@@ -31,7 +31,7 @@ const codeReuseSchema = z.object({
31
31
  verify: z
32
32
  .boolean()
33
33
  .default(false)
34
- .describe("Verify a previously refactored test instead of returning the reuse prompt. POM path (browser tests): checks the test's POM calls against source; requires the discovery pass — this tool called for the same testFile without `verify` — to have run first. SkyrampUtils path (every other test type): stages the shared utils file for the output commit, checks its invariants, and — when skyramp_modularization recorded a baseline for this spec — compares the delivered assertion count and page.on('pageerror') guard against that hand-out, rejecting a net loss (API helpers: one helper per method+path, status-code-only assertions, method+resource names; browser helpers: actions and structural waits only, intent names). Call it after the reuse edits are written; skyramp_enhance_assertions and skyramp_execute_test refuse until it has passed."),
34
+ .describe("Verify a previously refactored test instead of returning the reuse prompt. POM path (browser tests): checks the test's POM calls against source; requires the discovery pass — this tool called for the same testFile without `verify` — to have run first. SkyrampUtils path (every other test type): stages the shared utils file for the output commit, checks its invariants, and — when skyramp_modularization recorded a baseline for this spec — compares the delivered assertion count and page.on('pageerror') guard against that hand-out, rejecting a net loss (API helpers: one helper per method+path, status-code-only assertions, method+resource names; browser helpers: actions and structural waits only, intent names, one helper per action sequence; every family: one name is defined once per module, with one parameter count across modules). Call it after the reuse edits are written; skyramp_enhance_assertions and skyramp_execute_test refuse until it has passed."),
35
35
  testType: z
36
36
  .nativeEnum(TestType)
37
37
  .optional()
@@ -21,7 +21,7 @@ export interface FlaggedMember {
21
21
  * moment the spec changes — and it does change: the execution fix-up may restore
22
22
  * `<testFile>.raw.bak` over the spec by plain `cp`, invisible to this tool. The
23
23
  * counts and the decline list are therefore derived at report time (see
24
- * `rederiveReuseOutcome`), never read back from here.
24
+ * `rederiveReuse`), never read back from here.
25
25
  *
26
26
  * And keeping the two types disjoint makes the report boundary opt-IN. When the
27
27
  * record extended the wire type, every field added here shipped to the
@@ -1,13 +1,14 @@
1
1
  import { type VerifyResult } from "../../utils/pom-verify/index.js";
2
2
  import { type ReuseOutcome, type ReuseRecord } from "./reuse-outcome.js";
3
3
  import { type UtilsVerifyResult } from "../../utils/utils-verify/index.js";
4
+ import type { ReuseVerificationFailure } from "../../types/ReuseOutcome.js";
4
5
  /**
5
6
  * Record the tier-1 candidate count STEP 1 detected, plus the identity needed to
6
7
  * re-derive the counts from the delivered spec later.
7
8
  *
8
9
  * The identity is written here and not only by {@link recordVerifyOutcome} because
9
10
  * the verify pass is a call the agent decides whether to make. When it skips it,
10
- * a record holding the candidate count alone makes `rederiveReuseOutcome` bail out,
11
+ * a record holding the candidate count alone makes `rederiveReuse` bail out,
11
12
  * and the report states "N candidate POM files detected" with no reuse numbers —
12
13
  * indistinguishable from a run where reuse was measured and simply not summarized.
13
14
  * Recording it unconditionally is what makes the zero-reuse case reportable without
@@ -55,9 +56,11 @@ export declare function recordVerifyOutcome(testFile: string, r: VerifyResult, g
55
56
  * NOT terminal. A PASS is always reachable in one edit plus one call (substitute the
56
57
  * members, or document each candidate with a `// kept inline:` marker, which the gate
57
58
  * accepts), and an agent that resolves neither can still walk past these tools to
58
- * `skyramp_submit_report`, which does not block and reports the honest zero. So the
59
- * worst case degrades to the reporting half rather than to a run that produces
60
- * nothing — which is what a bound would have been protecting against.
59
+ * `skyramp_submit_report`. That tool refuses a blocking verdict on the delivered
60
+ * files too, but its refusal is BOUNDED (`REUSE_SUBMIT_MAX_REFUSALS`): past the bound
61
+ * it accepts the report with the fault recorded in the row. So the worst case still
62
+ * degrades to the reporting half rather than to a run that produces nothing — which
63
+ * is what a bound here would have been protecting against.
61
64
  */
62
65
  export declare function pendingReuseVerification(testFile: string, explicitStateFile?: string): Promise<string | undefined>;
63
66
  /**
@@ -81,7 +84,17 @@ export declare function recordUtilsVerifyError(testFile: string, explicitStateFi
81
84
  export declare function samePath(a: string, b: string): boolean;
82
85
  /** Exported for retrofit-state: the same canonicalisation the records use. */
83
86
  export declare function canonPath(p: string): string;
84
- /** The basename label a report row shows for a set of utils files. */
87
+ /**
88
+ * The label a report row shows for a set of utils files: each file's path relative
89
+ * to its repository root, posix-separated, joined with `, `.
90
+ *
91
+ * Repository-relative, never absolute — an absolute path carries the runner's
92
+ * temporary directory and means nothing to a customer. Not a basename either: a
93
+ * fullstack delivery writes one module per test directory under the same
94
+ * conventional name, and two rows labelled `skyrampUtils.ts` cannot be told apart
95
+ * (a renderer keyed on the label then merges them into one aggregate). Outside a
96
+ * repository, or for a file the root does not contain, the basename stands in.
97
+ */
85
98
  export declare function utilsFileLabel(files: string[]): string;
86
99
  /** Verdict of one utils verify pass. The verdict alone is stored — counts and the
87
100
  * file list are re-derived from the delivered files at report time. */
@@ -148,6 +161,64 @@ export declare function reuseChainSkipped(fileName: string, testType: string, re
148
161
  testType: string;
149
162
  language: string;
150
163
  }> | undefined): Promise<true | undefined>;
164
+ /**
165
+ * A blocking reuse verdict measured on the DELIVERED files at report time — the
166
+ * same predicate the live `verify: true` pass refuses on, re-run on what ships.
167
+ *
168
+ * Verification is a one-time checkpoint on a file that keeps changing: the execution
169
+ * fix loop deletes a failing assertion after the gate recorded `passed`, and a
170
+ * post-verification full-file rewrite erases verified page-object reuse. Until this
171
+ * existed, the report re-derived the verdict, found the fault, wrote it into a field
172
+ * and shipped the work — the run committed the fault, detected it, disclosed it and
173
+ * delivered it. `skyramp_submit_report` refuses on this instead, and the agent
174
+ * repairs (or documents the decline) and resubmits.
175
+ *
176
+ * Only a BLOCKING verdict lands here. Advisories (`scenario-name`, untyped
177
+ * parameters, sibling inline call sites, single importers) never do: they are
178
+ * report-time signals whose number means something only on the final file, and
179
+ * they must stay free to be computed and reported at submit.
180
+ */
181
+ export interface ReuseBlockingVerdict {
182
+ /** ABSOLUTE PATH of the spec the verdict is about — the refusal is agent-facing,
183
+ * and two specs sharing a basename (one per repository) must stay two verdicts. */
184
+ file: string;
185
+ /** The blocking kinds found, each with its count and detail — what the row ships
186
+ * with once the refusal bound is reached. */
187
+ failures: ReuseVerificationFailure[];
188
+ /** What failed and how to repair it — the text the live verify pass would have
189
+ * returned for the delivered file, so the remediation is the one the agent knows. */
190
+ detail: string;
191
+ /** The exact `skyramp_reuse_code` verify call to make after the repair, so the
192
+ * repaired files are re-checked. */
193
+ verifyCall: string;
194
+ }
195
+ /**
196
+ * How many times skyramp_submit_report refuses a report over one spec's blocking
197
+ * verdict before accepting it with the fault recorded in the row.
198
+ *
199
+ * The refusal is a repair loop, and a repair loop at the last step of a run needs a
200
+ * terminal state (SKYR-4059: an unbounded loop destroyed verified work). Without one,
201
+ * an agent that cannot reach a pass — out of turns, or a `guard-removed` it cannot
202
+ * restore, which no marker documents away — delivers no report at all, and a
203
+ * customer with no pull request is a worse outcome than one with a disclosed fault.
204
+ * Two refusals cover the common case, where the repair is one edit and one verify
205
+ * call away; the third call ships the row with `verificationFailures` set.
206
+ */
207
+ export declare const REUSE_SUBMIT_MAX_REFUSALS = 2;
208
+ /** What one report row's re-derivation established: the outcome to publish, and a
209
+ * blocking verdict when the delivered files fail the live check. */
210
+ export interface ReuseRederivation {
211
+ outcome: ReuseOutcome | undefined;
212
+ blocking?: ReuseBlockingVerdict;
213
+ }
214
+ /**
215
+ * The reason behind a failed shared-helper verdict, one entry per blocking kind in
216
+ * the order the verifier found them. The verify pass had these in hand and dropped
217
+ * them after computing `ok`; without them a `failed` row is a verdict nobody can act
218
+ * on. The detail text is the verifier's own — for an assertion loss it carries the
219
+ * counts (baseline, delivered, in the test, in the helpers) that decided the verdict.
220
+ */
221
+ export declare function verificationFailures(r: UtilsVerifyResult): ReuseVerificationFailure[];
151
222
  /**
152
223
  * Re-derive a recorded outcome from the spec as it stands NOW, so the report
153
224
  * describes the delivered artifact rather than the state at verify time.
@@ -169,8 +240,15 @@ export declare function reuseChainSkipped(fileName: string, testType: string, re
169
240
  *
170
241
  * Returns the recorded outcome minus internals when re-derivation is impossible
171
242
  * (no path recorded — now only the no-POM-layer path, which has no spec to measure
172
- * and no candidates to contrast against), and `undefined` when it fails outright.
243
+ * and no candidates to contrast against), and no outcome when it fails outright.
173
244
  * Failing closed matters: falling back to the recorded counts is exactly the false
174
245
  * claim this exists to prevent.
246
+ *
247
+ * Beside the outcome comes the blocking verdict the delivered files earn under the
248
+ * live check — see {@link ReuseBlockingVerdict}. One measurement feeds both: the row
249
+ * the report publishes and the refusal `skyramp_submit_report` returns. That half
250
+ * fails OPEN like every reuse check: a re-derivation that throws yields no outcome
251
+ * and no verdict. A verifier that cannot run is not evidence against the file, and
252
+ * refusing a report on a malfunction would deliver nothing to the customer.
175
253
  */
176
- export declare function rederiveReuseOutcome(record: ReuseRecord): Promise<ReuseOutcome | undefined>;
254
+ export declare function rederiveReuse(record: ReuseRecord): Promise<ReuseRederivation>;