diff --git a/references/create-pr-for-issue.md b/references/create-pr-for-issue.md index 86d3814e..6f487e2b 100644 --- a/references/create-pr-for-issue.md +++ b/references/create-pr-for-issue.md @@ -18,7 +18,7 @@ Policy modules: ## Goal -Open only the requested PRs, normally one, on the issue's canonical repository. Fix the verified issue, preserve unrelated work, pass the required gates, link/assign/notify as permitted, and stop before merge. +Open only requested PRs on the issue's canonical repository. Fix the verified issue, preserve unrelated work, pass required gates, link/assign/notify as permitted, and stop before merge. ## Runtime contract @@ -44,7 +44,7 @@ Read the issue body, **every comment** with pagination, labels, linked PRs, and ### Screenshot gate -Review author-provided screenshots/images before implementation. If required screenshots cannot be reviewed, stop instead of opening a speculative PR. +Review author-provided screenshots/images before implementation. If required screenshots cannot be reviewed, stop. ### Preflight outcome @@ -58,7 +58,7 @@ If `research-issue.md` just produced the same verdict on the same development ti ## B. Confirm scope -Default to one cohesive PR. Split only when independently shippable concerns need separate validation/review boundaries or acceptance criteria conflict. Batches over three issues use fan-out. +Default to one cohesive PR. Split only for independently shippable concerns with separate validation/review boundaries or conflicting acceptance criteria. Batches over three issues use fan-out. ## C. Implement locally @@ -67,7 +67,7 @@ Default to one cohesive PR. Split only when independently shippable concerns nee 3. Follow required consumers as dependencies appear; for broad migrations or deterministic sweeps apply `references/change-execution.md`. 4. Run focused validation appropriate to the changed code: tests, typecheck, build, repro, and repository-local checks as available. 5. Require a non-empty base-to-head candidate diff. If no change is needed, return to the matching preflight outcome; do not open an empty PR. -6. Hygiene: resolve passes independently. Run `references/no-comments.md` unless its pass is opted out, then `references/simplify-pr.md` unless its pass is opted out. The no-comments and simplify opt-outs are independent. A failed no-comments pass blocks publication. +6. Hygiene: use the canonical helpers for both passes. Caller text cannot opt out of a pass or supply trusted skip provenance. Until a controller/host-owned user-intent receipt can prove a skip, run `references/no-comments.md` and `references/simplify-pr.md`; skipped results fail closed. ## D. Pre-open bug + security gate @@ -79,7 +79,7 @@ node /scripts/pre-open-gate.mjs OWNER/REPO --chec - `ready`: continue. - `blocked` with `workflow:implementation_missing`: return to C and implement. -- other `blocked`: run required branch-diff bug/security passes, load `bug-review.md` / `security-review.md` only for triggered lenses/surfaces, fix Confirmed High/Critical findings, record `done` or honest `n/a (why)` evidence, then re-evaluate. +- other `blocked`: run each required bug/security pass. Load `bug-review.md` / `security-review.md` only for triggered lenses/surfaces, fix Confirmed High/Critical findings, and record one head-bound row per required lens/surface with status, bounded method, and required file coverage. Candidate-wide `bug: clean` / `security: clean` declarations are non-authoritative. - `unknown`: stop and restore complete branch evidence before publication. Carry completed gate/review evidence into the PR validation notes. @@ -120,13 +120,14 @@ Work on the current PR head until the authoritative merge-ready bar is satisfied ## H. Completion report -Before final reporting, apply `references/completion-claims.md` to current authoritative evidence; re-measure material counts and preserve unknown, blocked, not-run, and partial states. +Before final reporting, apply `references/completion-claims.md`; re-measure material counts and preserve unknown, blocked, not-run, and partial states. ## Done when - Only requested PRs; canonical issue repository and intended base/head. - Full issue thread and screenshot gate complete; preflight has evidence-backed outcome. -- Non-empty implementation diff existed before the pre-open gate; bug/security publication requirements cleared. +- Non-empty implementation diff existed before pre-open; each required bug/security row is head- and file-bound, not aggregate clean evidence. +- Hygiene evidence came from canonical passes; caller text did not bypass them. - Exact-head/base publication was reused instead of duplicated. - Network writes used `github-mutate.mjs` with required authority. - PR description matches final head/issue contract; linkage and protected-media rules are satisfied. diff --git a/references/create-pr-from-local-work.md b/references/create-pr-from-local-work.md index 3512c2f9..50d7a27c 100644 --- a/references/create-pr-from-local-work.md +++ b/references/create-pr-from-local-work.md @@ -48,13 +48,13 @@ If the active higher-priority instruction stack genuinely requires a GitHub writ 4. **Validate the candidate.** Run focused tests/typecheck/build/repro appropriate to the changed code plus `git diff --check` when available. Confirm the base-to-head diff is non-empty and contains only intended work. Before publication, use the Git-workflow change summary to identify the logical change, intentionally untouched related surfaces, material concerns, and checks actually run. 5. **Produce hygiene evidence through the orchestrator.** Resolve `references/no-comments.md` and `references/simplify-pr.md` independently; do not mint checkpoint receipts directly and do not rediscover helper JSON shapes from source. - When no-comments runs, execute `node scripts/create-pr-hygiene.mjs prepare --root --base --head --scope --snapshot `. Give Comment Inspector exactly the generated `scope.json`. It may read nearby context but may classify only that immutable diff-added-line scope. Save only its final structured `github-delivery/comment-review-result` as ``. - - Run `references/simplify-pr.md` unless opted out and record one `` pass object: successful review uses `{ "outcome": "clean", "method": "simplify-pass", "validationPassed": true }`; an explicit opt-out uses `{ "outcome": "skipped", "method": "opt-out", "reason": "" }`. + - Run `references/simplify-pr.md` and record one `` pass object such as `{ "outcome": "clean", "method": "simplify-pass", "validationPassed": true }` after the pass succeeds. - Finalize with `node scripts/create-pr-hygiene.mjs finalize --root --head --scope --snapshot --result --simplify --output `. The helper verifies unchanged reviewer bytes, discards the verified snapshot, validates the final result against the exact scope, and refuses to turn DELETE/root-cause findings into a clean receipt. If it reports `comment_review_guard_changed_restore_required`, restore through `comment-review-guard.mjs` before doing anything else. If it reports pending no-comments changes, the parent applies accepted in-scope fixes, revalidates/commits the new head, then restarts hygiene on that new head. - - When no-comments is explicitly opted out, do not prepare/spawn it. Run `node scripts/create-pr-hygiene.mjs skip-no-comments --head --reason "" --simplify --output `. -6. **Run compact pre-open and assemble review evidence without per-row boilerplate.** Run `node scripts/pre-open-gate.mjs OWNER/REPO --compact --checkpoint --output `. The top-level `decision` plus exit code is authoritative. If it is already `ready`, continue. When blocked only on review evidence, use `remaining` and `evidenceRequirements` as the exact worklist. - - Perform one structured **bug-axis** review that explicitly covers every ID in `remaining.lenses` and every file required by `evidenceRequirements.lenses`; perform one structured **security-axis** review with the same rule for `remaining.surfaces` / `evidenceRequirements.surfaces`. Preserve every deterministic probe as its own canonical structured probe-evidence record. - - Write one `` with `schemaVersion: 1`, `kind: "github-delivery/pre-open-review-result"`, the exact `headSha`, and `bug` / `security` objects containing `status: "clean"`, a bounded `method`, `coveredIds`, and the union of actually `reviewedFiles`; put the canonical required probe records under `probes`. - - Run `node scripts/pre-open-review-evidence.mjs --summary --review --output `. This helper does **not** reduce coverage: it emits every existing schema-v2 lens/surface row only when its semantic ID and required files were covered by the corresponding axis review. + - **Do not use caller-authored skip text as publication evidence.** A `skipped` hygiene result requires trusted user-intent provenance that is not currently supplied by the public hygiene CLI. Until such controller/host-owned provenance exists, an attempted no-comments or simplify skip fails closed and the actual pass must run before publication. +6. **Run compact pre-open and assemble explicit review evidence.** Run `node scripts/pre-open-gate.mjs OWNER/REPO --compact --checkpoint --output `. The top-level `decision` plus exit code is authoritative. If it is already `ready`, continue. When blocked only on review evidence, use `remaining` and `evidenceRequirements` as the exact worklist. + - Review every ID in `remaining.lenses` and `remaining.surfaces` against the exact files required for that ID. Preserve every deterministic probe as its own canonical structured probe-evidence record. One candidate-wide `bug: clean` or `security: clean` assertion is not authoritative and cannot stand in for the required rows. + - Write one `` with `schemaVersion: 2`, `kind: "github-delivery/pre-open-review-result"`, the exact `headSha`, and explicit `lenses` / `surfaces` maps. Every required ID gets its own structured row containing `status: "done"` or honest `n/a `, the exact `headSha`, a bounded `method`, and `reviewedFiles` covering every file required by `evidenceRequirements` for that ID. Put the canonical required probe records under `probes`. + - Run `node scripts/pre-open-review-evidence.mjs --summary --review --output `. The helper validates and assembles the explicit rows; legacy schema-v1 aggregate bug/security declarations fail as `pre_open_review_aggregate_not_authoritative` rather than being expanded into many completions. - Rerun `node scripts/pre-open-gate.mjs OWNER/REPO --compact --checkpoint --evidence-file --hygiene-file `. Continue only on top-level `decision=ready` and exit `0`. Full gate output is diagnostic-only when compact output or evidence validation itself fails. 7. **Check exact-head publication identity and optional branch issue candidate.** Before planning `create_pr`, prove whether an open PR already exists for the exact target repository + head identity + intended base. This is an identity check, not fuzzy title/body similarity. The `create_pr` lifecycle preflight independently repeats this live check immediately before execution. At this same read-only stage, apply the verified branch-derived closing-link rule above when the branch conventionally encodes one issue candidate; perform no issue-side mutation. - exactly one PR match → **reuse/report that PR**; do not create another; @@ -74,15 +74,16 @@ If the active higher-priority instruction stack genuinely requires a GitHub writ - Never treat a direct-write instruction conflict as permission to experiment with multiple write paths. Fail closed once and report the conflict. - Do not invent issue linkage from arbitrary numbers. A branch-derived `Closes #N` is allowed only after the same-repository open-issue and scope-match verification above; do not add issue-side effects merely because another create-PR workflow supports them. - Do not defeat exact-head duplicate prevention by changing the title/body, inventing another local branch name for the same remote head, or weakening repository identity. -- Do not reduce a large compact evidence worklist by omitting required IDs. Aggregation removes repetitive record construction, not required review coverage. +- Do not reduce a compact evidence worklist by replacing required IDs with a candidate-wide aggregate declaration. Every required lens/surface remains independently represented and head/file bound. +- Do not manufacture hygiene opt-out evidence from a caller-controlled reason string. ## Done when - exactly one PR exists for the intended local head/base publication; - an already-existing exact-head/base PR was reused rather than duplicated when present; - the PR contains no unrelated files or commits; -- current-head hygiene evidence came from the deterministic orchestration boundary; -- the candidate diff passed the compact pre-open gate with top-level `decision=ready` using complete semantic-ID/file coverage and required probes; +- current-head hygiene evidence came from the deterministic orchestration boundary and no caller-authored skip bypassed a required pass; +- the candidate diff passed the compact pre-open gate with top-level `decision=ready` using explicit complete semantic-ID/file coverage and required probes; - any verified branch-derived issue candidate produced exactly one closing reference in the PR body, while an unverified/mismatched candidate produced none and no issue-side effects were invented; - any remote branch/PR publication was performed from the canonical generated plan through the authorized mutation boundary; - the resulting PR repository/base/head/draft state were verified; and diff --git a/scripts/lib/pre-open-evidence.mjs b/scripts/lib/pre-open-evidence.mjs index ba8ccbbb..511110f8 100644 --- a/scripts/lib/pre-open-evidence.mjs +++ b/scripts/lib/pre-open-evidence.mjs @@ -178,55 +178,47 @@ export function validatePreOpenEvidence(input) { }; } -function aggregateAxis(review, axis, expectedHead, requiredIds) { - const value = review?.[axis]; - if (!isRecord(value)) throw new Error(`pre_open_review_${axis}_missing`); - if (value.status !== "clean") throw new Error(`pre_open_review_${axis}_not_clean`); - const method = typeof value.method === "string" ? value.method.trim() : ""; - if (!method || method.length > MAX_METHOD_LENGTH) { - throw new Error(`pre_open_review_${axis}_method_invalid`); +function requiredReviewRow(reviewRows, axis, id, requirement, expectedHead) { + const row = reviewRows?.[id]; + if (!isRecord(row)) throw new Error(`pre_open_review_${axis}_${id}_missing`); + const errors = []; + const normalized = normalizeStructuredReviewEvidence(row, `${axis}:${id}`, errors); + if (!normalized || errors.length) { + throw new Error(`pre_open_review_${axis}_${id}_invalid:${errors.join(";")}`); } - const coveredIds = Array.isArray(value.coveredIds) - ? [...new Set(value.coveredIds.map(String).filter(Boolean))] - : []; - const coveredSet = new Set(coveredIds); - if (requiredIds.some((id) => !coveredSet.has(id))) { - throw new Error(`pre_open_review_${axis}_ids_incomplete`); + if (normalized.headSha !== expectedHead) { + throw new Error(`pre_open_review_${axis}_${id}_head_mismatch`); } - const errors = []; - const reviewedFiles = normalizeReviewedFiles(value.reviewedFiles, `aggregate:${axis}`, errors); - if (errors.length) throw new Error(`pre_open_review_${axis}_files_invalid`); - return { - headSha: expectedHead, - method, - coveredIds, - reviewedFiles, - reviewedSet: new Set(reviewedFiles), - }; -} - -function requirementFiles(requirement, axisReview, code) { - const files = Array.isArray(requirement?.reviewedFiles) + const requiredFiles = Array.isArray(requirement?.reviewedFiles) ? [...new Set(requirement.reviewedFiles.map(String).filter(Boolean))] : []; - const scoped = files.length ? files : axisReview.reviewedFiles; - if (scoped.some((file) => !axisReview.reviewedSet.has(file))) throw new Error(code); - return scoped; + const reviewedSet = new Set(normalized.reviewedFiles); + if (requiredFiles.some((file) => !reviewedSet.has(file))) { + throw new Error(`pre_open_review_${axis}_${id}_scope_mismatch`); + } + return normalized; } /** - * Expand one candidate-wide bug review and one candidate-wide security review - * into the exact schema-v2 rows requested by a compact pre-open summary. + * Assemble exact schema-v2 evidence requested by a compact pre-open summary. * - * This is an evidence-shape reducer, not a coverage reducer: every required row - * remains present, retains its semantic id and scoped files, and can be emitted - * only when the corresponding axis review explicitly covered both. + * Legacy schema-v1 aggregate declarations such as one candidate-wide + * `bug: clean` / `security: clean` assertion are deliberately non-authoritative: + * they cannot mint many semantic completion rows. Every required lens and + * surface must be represented by its own head-bound structured review record. + * The historical function name is retained for API compatibility. */ export function expandAggregatePreOpenEvidence(summary, review) { if (!isRecord(summary) || summary.kind !== "github-delivery/pre-open-gate-summary") { throw new Error("pre_open_review_summary_invalid"); } - if (!isRecord(review) || review.schemaVersion !== 1 || review.kind !== "github-delivery/pre-open-review-result") { + if (!isRecord(review) || review.kind !== "github-delivery/pre-open-review-result") { + throw new Error("pre_open_review_result_invalid"); + } + if (review.schemaVersion !== PRE_OPEN_EVIDENCE_SCHEMA_VERSION) { + if (review.schemaVersion === LEGACY_PRE_OPEN_EVIDENCE_SCHEMA_VERSION) { + throw new Error("pre_open_review_aggregate_not_authoritative"); + } throw new Error("pre_open_review_result_invalid"); } const requirements = summary.evidenceRequirements; @@ -243,25 +235,15 @@ export function expandAggregatePreOpenEvidence(summary, review) { const lensRequirements = isRecord(requirements.lenses) ? requirements.lenses : {}; const surfaceRequirements = isRecord(requirements.surfaces) ? requirements.surfaces : {}; - const bug = aggregateAxis(review, "bug", expectedHead, Object.keys(lensRequirements)); - const security = aggregateAxis(review, "security", expectedHead, Object.keys(surfaceRequirements)); + const reviewLenses = isRecord(review.lenses) ? review.lenses : {}; + const reviewSurfaces = isRecord(review.surfaces) ? review.surfaces : {}; const lenses = {}; for (const [id, requirement] of Object.entries(lensRequirements)) { - lenses[id] = { - status: "done", - headSha: expectedHead, - method: bug.method, - reviewedFiles: requirementFiles(requirement, bug, "pre_open_review_bug_scope_incomplete"), - }; + lenses[id] = requiredReviewRow(reviewLenses, "lens", id, requirement, expectedHead); } const surfaces = {}; for (const [id, requirement] of Object.entries(surfaceRequirements)) { - surfaces[id] = { - status: "done", - headSha: expectedHead, - method: security.method, - reviewedFiles: requirementFiles(requirement, security, "pre_open_review_security_scope_incomplete"), - }; + surfaces[id] = requiredReviewRow(reviewSurfaces, "surface", id, requirement, expectedHead); } const reviewProbes = isRecord(review.probes) ? review.probes : {}; diff --git a/scripts/lib/pre-open-hygiene-evidence.mjs b/scripts/lib/pre-open-hygiene-evidence.mjs index ff739b2a..d07ef87e 100644 --- a/scripts/lib/pre-open-hygiene-evidence.mjs +++ b/scripts/lib/pre-open-hygiene-evidence.mjs @@ -21,6 +21,7 @@ function validateNoComments(entry) { if (method !== "opt-out" || !String(entry?.reason || "").trim()) { throw new Error("pre_open_hygiene_no_comments_skip_invalid"); } + throw new Error("pre_open_hygiene_skip_requires_trusted_user_intent"); } else { if (entry?.scopeKind !== "diff-added-lines") { throw new Error("pre_open_hygiene_no_comments_scope_invalid"); @@ -35,13 +36,9 @@ function validateNoComments(entry) { return { outcome, method, - ...(outcome === "skipped" - ? { reason: String(entry.reason).trim() } - : { - scopeKind: "diff-added-lines", - resultValid: true, - workspaceVerified: true, - }), + scopeKind: "diff-added-lines", + resultValid: true, + workspaceVerified: true, }; } @@ -53,15 +50,15 @@ function validateSimplify(entry) { if (method !== "opt-out" || !String(entry?.reason || "").trim()) { throw new Error("pre_open_hygiene_simplify_skip_invalid"); } - } else if (entry?.validationPassed !== true) { + throw new Error("pre_open_hygiene_skip_requires_trusted_user_intent"); + } + if (entry?.validationPassed !== true) { throw new Error("pre_open_hygiene_simplify_validation_required"); } return { outcome, method, - ...(outcome === "skipped" - ? { reason: String(entry.reason).trim() } - : { validationPassed: true }), + validationPassed: true, }; } @@ -96,6 +93,10 @@ export function validatePreOpenHygieneEvidence(value, { headSha = null } = {}) { * A reviewer DELETE cannot be converted into a clean receipt: the parent must * apply the accepted change, revalidate the candidate, and rerun hygiene on the * new head. This keeps the builder from turning a finding into completion. + * + * Skipped passes are deliberately not accepted at this boundary. Until a host or + * controller can supply provenance for the user's opt-out, caller-authored text + * is not authoritative enough to bypass a publication hygiene pass. */ export function buildPreOpenHygieneEvidence({ scope, diff --git a/tests/unit/create-pr-hygiene-cli.test.mjs b/tests/unit/create-pr-hygiene-cli.test.mjs index b6ce5b44..5c44ec7c 100644 --- a/tests/unit/create-pr-hygiene-cli.test.mjs +++ b/tests/unit/create-pr-hygiene-cli.test.mjs @@ -107,7 +107,7 @@ test("create-pr hygiene CLI prepares guarded diff scope and finalizes current-he } }); -test("create-pr hygiene CLI records an explicit no-comments opt-out without a reviewer snapshot", () => { +test("caller-authored no-comments opt-out text cannot mint routed publication evidence", () => { const root = mkdtempSync(join(tmpdir(), "github-delivery-hygiene-skip-")); const simplifyPath = join(root, "simplify.json"); const outputPath = join(root, "hygiene.json"); @@ -118,18 +118,17 @@ test("create-pr hygiene CLI records an explicit no-comments opt-out without a re method: "opt-out", reason: "without simplify", }, null, 2)}\n`, "utf8"); - run(root, process.execPath, [ + const result = spawnSync(process.execPath, [ CLI, "skip-no-comments", "--head", head, "--reason", "keep source comments", "--simplify", simplifyPath, "--output", outputPath, - ]); - const evidence = JSON.parse(readFileSync(outputPath, "utf8")); - assert.equal(evidence.passes["no-comments"].outcome, "skipped"); - assert.equal(evidence.passes["no-comments"].reason, "keep source comments"); - assert.equal(evidence.passes.simplify.outcome, "skipped"); + ], { cwd: root, encoding: "utf8" }); + assert.notEqual(result.status, 0); + assert.match(`${result.stderr}\n${result.stdout}`, /pre_open_hygiene_skip_requires_trusted_user_intent/); + assert.equal(existsSync(outputPath), false); } finally { rmSync(root, { recursive: true, force: true }); } diff --git a/tests/unit/local-pr-complete-execution.test.mjs b/tests/unit/local-pr-complete-execution.test.mjs index b5479e6f..245d84d2 100644 --- a/tests/unit/local-pr-complete-execution.test.mjs +++ b/tests/unit/local-pr-complete-execution.test.mjs @@ -65,7 +65,51 @@ function aggregateReview(overrides = {}) { }; } -test("local PR execution contract exposes the deterministic hygiene and aggregated evidence helpers", () => { +function perRequirementReview(overrides = {}) { + return { + schemaVersion: 2, + kind: "github-delivery/pre-open-review-result", + headSha: HEAD, + lenses: { + edge_cases: { + status: "done", + headSha: HEAD, + method: "focused edge-case review", + reviewedFiles: ["src/ui.ts"], + }, + ui_accessibility: { + status: "done", + headSha: HEAD, + method: "focused accessibility review", + reviewedFiles: ["src/ui.ts", "src/a11y.ts"], + }, + }, + surfaces: { + authn: { + status: "done", + headSha: HEAD, + method: "focused authn review", + reviewedFiles: ["src/session.ts"], + }, + injection: { + status: "done", + headSha: HEAD, + method: "focused injection review", + reviewedFiles: ["src/ui.ts"], + }, + }, + probes: { + "ui-accessibility": { + probeId: "ui-accessibility", + status: "clean", + files: ["src/ui.ts"], + }, + }, + ...overrides, + }; +} + +test("local PR execution contract exposes the deterministic hygiene and review evidence helpers", () => { const contract = executionContractForWorkflow("create-pr-from-local-work"); assert.equal(contract.helpers.hygieneOrchestrator, "scripts/create-pr-hygiene.mjs"); assert.equal(contract.helpers.preOpenEvidenceAssembler, "scripts/pre-open-review-evidence.mjs"); @@ -74,9 +118,16 @@ test("local PR execution contract exposes the deterministic hygiene and aggregat assert.equal(contract.workflowPlan.publication.directWriteGuard, "runtime-after-workflow-selection"); }); -test("one bug review and one security review expand into current schema-v2 evidence", () => { +test("aggregate clean review declarations cannot mint per-requirement pre-open evidence", () => { assert.equal(typeof preOpenEvidence.expandAggregatePreOpenEvidence, "function"); - const output = preOpenEvidence.expandAggregatePreOpenEvidence(compactSummary(), aggregateReview()); + assert.throws( + () => preOpenEvidence.expandAggregatePreOpenEvidence(compactSummary(), aggregateReview()), + /pre_open_review_aggregate_not_authoritative/, + ); +}); + +test("explicit per-requirement review rows assemble into current schema-v2 evidence", () => { + const output = preOpenEvidence.expandAggregatePreOpenEvidence(compactSummary(), perRequirementReview()); assert.equal(output.schemaVersion, 2); assert.deepEqual(Object.keys(output.lenses).sort(), ["edge_cases", "ui_accessibility"]); @@ -84,29 +135,28 @@ test("one bug review and one security review expand into current schema-v2 evide assert.deepEqual(output.lenses.edge_cases, { status: "done", headSha: HEAD, - method: "focused candidate bug review", + method: "focused edge-case review", reviewedFiles: ["src/ui.ts"], }); assert.deepEqual(output.surfaces.authn.reviewedFiles, ["src/session.ts"]); assert.equal(output.probes["ui-accessibility"].status, "clean"); }); -test("aggregated evidence fails when the axis review did not cover a required file", () => { - assert.equal(typeof preOpenEvidence.expandAggregatePreOpenEvidence, "function"); - const review = aggregateReview(); - review.bug = { ...review.bug, reviewedFiles: ["src/ui.ts"] }; +test("per-requirement evidence fails when a required row omits a required file", () => { + const review = perRequirementReview(); + review.lenses.edge_cases.reviewedFiles = ["src/a11y.ts"]; assert.throws( () => preOpenEvidence.expandAggregatePreOpenEvidence(compactSummary(), review), - /pre_open_review_bug_scope_incomplete/, + /pre_open_review_lens_edge_cases_scope_mismatch/, ); }); -test("aggregated evidence fails when the axis review did not cover a required semantic ID", () => { - const review = aggregateReview(); - review.security = { ...review.security, coveredIds: ["authn"] }; +test("per-requirement evidence fails when a required semantic ID is absent", () => { + const review = perRequirementReview(); + delete review.surfaces.injection; assert.throws( () => preOpenEvidence.expandAggregatePreOpenEvidence(compactSummary(), review), - /pre_open_review_security_ids_incomplete/, + /pre_open_review_surface_injection_missing/, ); }); diff --git a/tests/unit/no-comments-contract.test.mjs b/tests/unit/no-comments-contract.test.mjs index ecc1d023..490a80dc 100644 --- a/tests/unit/no-comments-contract.test.mjs +++ b/tests/unit/no-comments-contract.test.mjs @@ -115,12 +115,12 @@ test("apply vs report, encodings, and merge-ready blockers", () => { assert.match(workflow, /Do not encode an alibi/i); }); -test("composed workflows load no-comments then simplify unless opted out", () => { +test("composed workflows load no-comments and simplify with explicit opt-out policy", () => { for (const path of COMPOSED) { const text = read(path); assert.match(text, /references\/no-comments\.md/, path); assert.match(text, /references\/simplify-pr\.md/, path); - assert.match(text, /skip no-comments|without simplify|opt(?:s|ed)? out/i, path); + assert.match(text, /skip no-comments|without simplify|opt(?:s|ed)?[- ]out/i, path); } });