diff --git a/package.json b/package.json index 3f4b6a1e8d..a7bdf30c93 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "test:smoke:observability": "node scripts/smoke-observability-traces.mjs", "test:smoke:browser:install": "playwright install chromium", "test:smoke:browser": "node scripts/smoke-ui-browser.mjs", - "test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run manifest:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build", + "test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:engine-parity && npm run test --workspace @jsonbored/gittensory-engine && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run manifest:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build", "test:release": "npm run test:ci && npm run changelog:check", "test:release:mcp": "npm run test:ci && npm run changelog:check:mcp", "test:watch": "vitest", diff --git a/packages/gittensory-engine/test/ai-policy-map.test.ts b/packages/gittensory-engine/test/ai-policy-map.test.ts index aac96a08a1..a85f906330 100644 --- a/packages/gittensory-engine/test/ai-policy-map.test.ts +++ b/packages/gittensory-engine/test/ai-policy-map.test.ts @@ -495,6 +495,7 @@ test("renderAiPolicyFatigueMarkdown escapes summaries and policy source fields", test("resolveAiPolicyFatigueVerdict is byte-stable for the same metadata", () => { const input = { + repoFullName: "JSONbored/gittensory", now: NOW, docs: { aiUsage: null, contributing: "Normal guide." }, pullRequests: [aiPr({ id: "a" }), aiPr({ id: "b" })], diff --git a/packages/gittensory-engine/test/duplicate-winner.test.ts b/packages/gittensory-engine/test/duplicate-winner.test.ts index 522cc38d21..ac4c9d6145 100644 --- a/packages/gittensory-engine/test/duplicate-winner.test.ts +++ b/packages/gittensory-engine/test/duplicate-winner.test.ts @@ -54,7 +54,13 @@ test("isDuplicateClusterWinnerByClaim: fails closed on an invalid claim timestam ); }); -test("isDuplicateClusterWinnerByClaim createdAt precedence: elects the PR GitHub says opened first, even when observed later", () => { +// #3956 (anti-backdating): createdAt is deliberately NOT part of the election (see +// prPrecedesSibling's doc comment in src/duplicate-winner.ts) -- an older PR could otherwise steal +// winner credit by editing its body to claim the linked issue later. This block replaces three +// stale "createdAt precedence" tests that asserted the OLD, since-removed createdAt-based ordering +// (root test/unit/duplicate-winner.test.ts was updated for the same fix in #3956; this package's +// own parallel test file wasn't, because it isn't part of test:ci and nobody noticed). +test("isDuplicateClusterWinnerByClaim: ignores createdAt even when present on both sides, decides purely by claim time", () => { const openedFirstButClaimedLater = { number: 9, createdAt: "2026-01-01T00:00:00Z", @@ -65,22 +71,15 @@ test("isDuplicateClusterWinnerByClaim createdAt precedence: elects the PR GitHub createdAt: "2026-01-02T00:00:00Z", linkedIssueClaimedAt: "2026-01-01T00:00:00Z", }; - assert.equal(isDuplicateClusterWinnerByClaim(openedFirstButClaimedLater, [openedSecondButClaimedFirst]), true); - assert.equal(isDuplicateClusterWinnerByClaim(openedSecondButClaimedFirst, [openedFirstButClaimedLater]), false); + // openedSecondButClaimedFirst claimed earlier, so it wins despite its later createdAt. + assert.equal(isDuplicateClusterWinnerByClaim(openedFirstButClaimedLater, [openedSecondButClaimedFirst]), false); + assert.equal(isDuplicateClusterWinnerByClaim(openedSecondButClaimedFirst, [openedFirstButClaimedLater]), true); }); -test("isDuplicateClusterWinnerByClaim createdAt precedence: falls back to claim-time when only one side has a valid createdAt", () => { - const modern = { number: 9, createdAt: "2026-01-01T00:00:00Z", linkedIssueClaimedAt: "2026-01-05T00:00:00Z" }; - const legacy = { number: 3, linkedIssueClaimedAt: "2026-01-02T00:00:00Z" }; - // Neither side has BOTH createdAt values, so this falls back to claim-time comparison: modern claimed later, so legacy wins. - assert.equal(isDuplicateClusterWinnerByClaim(legacy, [modern]), true); - assert.equal(isDuplicateClusterWinnerByClaim(modern, [legacy]), false); -}); - -test("isDuplicateClusterWinnerByClaim createdAt precedence: ties break by PR number", () => { - const a = { number: 3, createdAt: "2026-01-01T00:00:00Z" }; - const b = { number: 9, createdAt: "2026-01-01T00:00:00Z" }; - assert.equal(isDuplicateClusterWinnerByClaim(a, [b]), true); +test("isDuplicateClusterWinnerByClaim: still fails closed when createdAt is present but claim timing is missing", () => { + const a = { number: 12, createdAt: "2026-06-29T10:00:00.000Z" }; + const b = { number: 13, createdAt: "2026-06-29T10:05:00.000Z" }; + assert.equal(isDuplicateClusterWinnerByClaim(a, [b]), false); assert.equal(isDuplicateClusterWinnerByClaim(b, [a]), false); }); diff --git a/packages/gittensory-engine/test/gate-verdict-calibration.test.ts b/packages/gittensory-engine/test/gate-verdict-calibration.test.ts index f6abfe3758..90aa7bfb2c 100644 --- a/packages/gittensory-engine/test/gate-verdict-calibration.test.ts +++ b/packages/gittensory-engine/test/gate-verdict-calibration.test.ts @@ -9,6 +9,7 @@ import { resolveGateVerdictCalibrationConfig, scoreObjectiveAnchor, } from "../dist/index.js"; +import type { GateVerdictCalibrationIngestion } from "../dist/index.js"; test("barrel: exports structured gate-verdict calibration APIs (#3015)", () => { assert.equal(typeof resolveGateVerdictCalibrationConfig, "function"); @@ -475,35 +476,41 @@ test("renderGateVerdictCalibrationAuditMarkdown escapes markdown controls and co }); test("computeGateVerdictCompositeCalibrationScore sanitizes pre-ingested audit rows", () => { + // Deliberately malformed/untrusted input (extra rawReviewText/privateMetadata/trustScore fields + // an external, unsanitized source could send) -- the cast simulates data that arrived outside + // TypeScript's type system (e.g. JSON.parse of a webhook payload), which is exactly what + // isGateVerdictCalibrationIngestion + sanitizeGateVerdictCalibrationIngestion exist to validate + // and strip at runtime. + const gateVerdicts = { + accepted: [ + { + repoFullName: "JSONbored/Gittensory", + replayRunId: " replay-13 ", + gateRunId: "gate-13", + observedAt: "not an ISO timestamp", + score: 1, + dimensions: [ + { dimension: "correctness", outcome: "pass", confidence: 1, rawReviewText: "private" }, + { dimension: "trustScore", outcome: "pass", confidence: 1, privateMetadata: "private" }, + ], + rawReviewText: "private", + trustScore: 99, + }, + ], + rejected: [ + { + repoFullName: "JSONbored/Gittensory", + replayRunId: "replay-13", + gateRunId: "gate-13", + reason: "not_opted_in", + privateMetadata: "private", + }, + ], + } as unknown as GateVerdictCalibrationIngestion; const result = computeGateVerdictCompositeCalibrationScore({ objectiveAnchor: 0.5, pairwise: 0.5, - gateVerdicts: { - accepted: [ - { - repoFullName: "JSONbored/Gittensory", - replayRunId: " replay-13 ", - gateRunId: "gate-13", - observedAt: "not an ISO timestamp", - score: 1, - dimensions: [ - { dimension: "correctness", outcome: "pass", confidence: 1, rawReviewText: "private" }, - { dimension: "trustScore", outcome: "pass", confidence: 1, privateMetadata: "private" }, - ], - rawReviewText: "private", - trustScore: 99, - }, - ], - rejected: [ - { - repoFullName: "JSONbored/Gittensory", - replayRunId: "replay-13", - gateRunId: "gate-13", - reason: "not_opted_in", - privateMetadata: "private", - }, - ], - }, + gateVerdicts, }); const serialized = JSON.stringify(result); @@ -533,37 +540,40 @@ test("computeGateVerdictCompositeCalibrationScore sanitizes pre-ingested audit r }); test("computeGateVerdictCompositeCalibrationScore ignores malformed pre-ingested rows", () => { + // Same as above: deliberately invalid enum values ("trustScore" as a dimension, "private" as an + // outcome, "privateMetadata" as a rejection reason) simulating untrusted external input. + const gateVerdicts = { + accepted: [ + { + repoFullName: "not a repo", + replayRunId: "replay-14", + gateRunId: "gate-14", + observedAt: "2026-07-04T17:00:00.000Z", + score: 1, + dimensions: [{ dimension: "correctness", outcome: "pass", confidence: 1 }], + }, + { + repoFullName: "jsonbored/gittensory", + replayRunId: "replay-14", + gateRunId: "gate-14", + observedAt: "2026-07-04T17:00:00.000Z", + score: 1, + dimensions: [{ dimension: "rawReviewText", outcome: "private", confidence: 1 }], + }, + ], + rejected: [ + { + repoFullName: "jsonbored/gittensory", + replayRunId: "replay-14", + gateRunId: "gate-14", + reason: "privateMetadata", + }, + ], + } as unknown as GateVerdictCalibrationIngestion; const result = computeGateVerdictCompositeCalibrationScore({ objectiveAnchor: 0.5, pairwise: 0.5, - gateVerdicts: { - accepted: [ - { - repoFullName: "not a repo", - replayRunId: "replay-14", - gateRunId: "gate-14", - observedAt: "2026-07-04T17:00:00.000Z", - score: 1, - dimensions: [{ dimension: "correctness", outcome: "pass", confidence: 1 }], - }, - { - repoFullName: "jsonbored/gittensory", - replayRunId: "replay-14", - gateRunId: "gate-14", - observedAt: "2026-07-04T17:00:00.000Z", - score: 1, - dimensions: [{ dimension: "rawReviewText", outcome: "private", confidence: 1 }], - }, - ], - rejected: [ - { - repoFullName: "jsonbored/gittensory", - replayRunId: "replay-14", - gateRunId: "gate-14", - reason: "privateMetadata", - }, - ], - }, + gateVerdicts, }); assert.equal(result.structuredGateVerdictScore, null);