-
-
Notifications
You must be signed in to change notification settings - Fork 38
feat(marquette): add typed test-run admission decisions #3198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import assert from "node:assert/strict"; | ||
| import { readFileSync } from "node:fs"; | ||
| import test from "node:test"; | ||
|
|
||
| import type { TestRunEvidence } from "./test-run.js"; | ||
| import { admitTestRun, type TestRunCandidate } from "./test-run-admission.js"; | ||
| import { testRunAdmissionId } from "./test-run-canonical.js"; | ||
|
|
||
| function readEvidence(): TestRunEvidence { | ||
| return JSON.parse( | ||
| readFileSync( | ||
| new URL("../../../tests/fixtures/test-run-evidence/valid.json", import.meta.url), | ||
| "utf8", | ||
| ), | ||
| ) as TestRunEvidence; | ||
| } | ||
|
|
||
| function candidateFor(evidence: TestRunEvidence): TestRunCandidate { | ||
| return { | ||
| application: evidence.application, | ||
| environment: evidence.environment, | ||
| contractFingerprint: evidence.contractFingerprint, | ||
| sourceRevision: evidence.sourceRevision, | ||
| release: evidence.release, | ||
| artifactFingerprint: evidence.artifact.fingerprint, | ||
| }; | ||
| } | ||
|
|
||
| const NOW = "2026-07-22T00:00:00.000Z"; | ||
|
|
||
| function codes(diagnostics: { code: string }[]): string[] { | ||
| return diagnostics.map((diagnostic) => diagnostic.code); | ||
| } | ||
|
|
||
| void test("an exact candidate is admitted", async () => { | ||
| const evidence = readEvidence(); | ||
| const id = await testRunAdmissionId(evidence); | ||
| assert.deepEqual(await admitTestRun(evidence, candidateFor(evidence), id, NOW), []); | ||
| }); | ||
|
|
||
| void test("malformed admission ids are rejected", async () => { | ||
| const evidence = readEvidence(); | ||
| const rejected = await admitTestRun(evidence, candidateFor(evidence), "test-run:junk", NOW); | ||
| assert.deepEqual(codes(rejected), ["VIZE_MARQUETTE_141"]); | ||
| }); | ||
|
|
||
| void test("a different record cannot reuse an admission id", async () => { | ||
| const evidence = readEvidence(); | ||
| const id = await testRunAdmissionId(evidence); | ||
| const tampered = { | ||
| ...evidence, | ||
| suites: evidence.suites.map((suite, index) => | ||
| index === 0 ? { ...suite, passed: suite.passed - 1 } : suite, | ||
| ), | ||
| verification: { ...evidence.verification, passed: evidence.verification.passed - 1 }, | ||
| }; | ||
| assert.deepEqual(codes(await admitTestRun(tampered, candidateFor(evidence), id, NOW)), [ | ||
| "VIZE_MARQUETTE_142", | ||
| ]); | ||
| }); | ||
|
|
||
| void test("every candidate binding must match", async () => { | ||
| const evidence = readEvidence(); | ||
| const id = await testRunAdmissionId(evidence); | ||
| const candidate = { | ||
| ...candidateFor(evidence), | ||
| release: "0.999.0", | ||
| environment: "staging", | ||
| }; | ||
| assert.deepEqual(codes(await admitTestRun(evidence, candidate, id, NOW)), [ | ||
| "VIZE_MARQUETTE_144", | ||
| "VIZE_MARQUETTE_144", | ||
| ]); | ||
| }); | ||
|
|
||
| void test("expired records and malformed instants fail closed", async () => { | ||
| const evidence = readEvidence(); | ||
| const id = await testRunAdmissionId(evidence); | ||
| assert.deepEqual( | ||
| codes(await admitTestRun(evidence, candidateFor(evidence), id, evidence.validUntil)), | ||
| ["VIZE_MARQUETTE_145"], | ||
| ); | ||
| assert.deepEqual(codes(await admitTestRun(evidence, candidateFor(evidence), id, "yesterday")), [ | ||
| "VIZE_MARQUETTE_148", | ||
| ]); | ||
| }); | ||
|
|
||
| void test("skipped tests are not admitted", async () => { | ||
| const evidence = readEvidence(); | ||
| const skipped = { | ||
| ...evidence, | ||
| suites: evidence.suites.map((suite, index) => | ||
| index === 0 ? { ...suite, skipped: 1, passed: suite.passed - 1 } : suite, | ||
| ), | ||
| verification: { | ||
| ...evidence.verification, | ||
| skipped: 1, | ||
| passed: evidence.verification.passed - 1, | ||
| }, | ||
| }; | ||
| const id = await testRunAdmissionId(skipped); | ||
| assert.deepEqual(codes(await admitTestRun(skipped, candidateFor(skipped), id, NOW)), [ | ||
| "VIZE_MARQUETTE_147", | ||
| ]); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| import type { TestRunEvidence } from "./test-run-model.js"; | ||
| import type { MarquetteDiagnostic } from "./validate.js"; | ||
| import { parseTestRunAdmissionId, testRunFingerprint } from "./test-run-canonical.js"; | ||
| import { validateTestRunEvidence } from "./test-run-validate.js"; | ||
| import { error, isStrictTimestamp } from "./test-run-validate-rules.js"; | ||
|
|
||
| export { TEST_RUN_ADMISSION_PREFIX, parseTestRunAdmissionId } from "./test-run-canonical.js"; | ||
|
|
||
| /** | ||
| * Exact release candidate a deployment gate wants evidence for. | ||
| * | ||
| * Every field must match the record exactly; admission never falls back to a | ||
| * newer, older, or partially matching record. | ||
| */ | ||
| export interface TestRunCandidate { | ||
| /** Application the gate is deploying. */ | ||
| readonly application: string; | ||
| /** Deployment environment the gate is promoting into. */ | ||
| readonly environment: string; | ||
| /** Lowercase SHA-256 fingerprint of the application contract. */ | ||
| readonly contractFingerprint: string; | ||
| /** Exact source revision of the candidate. */ | ||
| readonly sourceRevision: string; | ||
| /** Release the candidate belongs to. */ | ||
| readonly release: string; | ||
| /** Lowercase SHA-256 fingerprint of the exact artifact being promoted. */ | ||
| readonly artifactFingerprint: string; | ||
| } | ||
|
|
||
| /** | ||
| * Decides whether one record admits one exact candidate at one instant. | ||
| * | ||
| * An empty result admits the deployment. Any diagnostic rejects it: the | ||
| * record must validate cleanly, its canonical fingerprint must be the one | ||
| * named by `admissionId`, every candidate binding must match exactly, the | ||
| * record must not be expired at `now`, the independent verification must | ||
| * have accepted the run, and no skipped test may remain unaccounted for. | ||
| * | ||
| * Codes, paths, messages, and ordering are identical to the native | ||
| * implementation, so both host families reach the same decision for the | ||
| * same record. | ||
| * | ||
| * `now` must be a millisecond-precision UTC timestamp such as | ||
| * `2026-01-01T00:00:00.000Z`; the fixed-width format keeps the expiry | ||
| * comparison exact. Callers own retrieval: fetch the canonical bytes from an | ||
| * immutable store within their own deadline, refuse oversized content, and | ||
| * hand the parsed record here. | ||
| */ | ||
| export async function admitTestRun( | ||
| evidence: TestRunEvidence, | ||
| candidate: TestRunCandidate, | ||
| admissionId: string, | ||
| now: string, | ||
| ): Promise<MarquetteDiagnostic[]> { | ||
| const diagnostics = validateTestRunEvidence(evidence); | ||
|
|
||
| const nowIsExact = isStrictTimestamp(now); | ||
| if (!nowIsExact) { | ||
| diagnostics.push( | ||
| error( | ||
| "VIZE_MARQUETTE_148", | ||
| "admission.now", | ||
| "admission time must be a millisecond-precision UTC instant", | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| const expected = parseTestRunAdmissionId(admissionId); | ||
| if (expected === undefined) { | ||
| diagnostics.push( | ||
| error( | ||
| "VIZE_MARQUETTE_141", | ||
| "admission.id", | ||
| "admission id must be test-run: followed by 64 lowercase hexadecimal characters", | ||
| ), | ||
| ); | ||
| } else if ((await testRunFingerprint(evidence)) !== expected) { | ||
| diagnostics.push( | ||
| error( | ||
| "VIZE_MARQUETTE_142", | ||
| "admission.id", | ||
| "admission id does not name this record's canonical fingerprint", | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| const bindings = [ | ||
| [evidence.application, candidate.application, "application"], | ||
| [evidence.environment, candidate.environment, "environment"], | ||
| [evidence.contractFingerprint, candidate.contractFingerprint, "contractFingerprint"], | ||
| [evidence.sourceRevision, candidate.sourceRevision, "sourceRevision"], | ||
| [evidence.release, candidate.release, "release"], | ||
| [evidence.artifact.fingerprint, candidate.artifactFingerprint, "artifact.fingerprint"], | ||
| ] as const; | ||
| for (const [recorded, wanted, field] of bindings) { | ||
| if (recorded !== wanted) { | ||
| diagnostics.push( | ||
| error("VIZE_MARQUETTE_144", field, `record does not bind the candidate ${field}`), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| if (nowIsExact && evidence.validUntil <= now) { | ||
| diagnostics.push( | ||
| error("VIZE_MARQUETTE_145", "validUntil", "record is expired at the admission time"), | ||
| ); | ||
| } | ||
| if (evidence.verification.outcome !== "accepted") { | ||
| diagnostics.push( | ||
| error( | ||
| "VIZE_MARQUETTE_146", | ||
| "verification.outcome", | ||
| "only an accepted verification can admit a deployment", | ||
| ), | ||
| ); | ||
| } | ||
| if (evidence.verification.skipped > 0) { | ||
| diagnostics.push( | ||
| error( | ||
| "VIZE_MARQUETTE_147", | ||
| "verification.skipped", | ||
| "skipped tests are not approved for deployment admission", | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| diagnostics.sort((left, right) => | ||
| left.path !== right.path | ||
| ? left.path < right.path | ||
| ? -1 | ||
| : 1 | ||
| : left.code !== right.code | ||
| ? left.code < right.code | ||
| ? -1 | ||
| : 1 | ||
| : left.message < right.message | ||
| ? -1 | ||
| : left.message > right.message | ||
| ? 1 | ||
| : 0, | ||
| ); | ||
|
Comment on lines
+127
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value Extract shared diagnostic sorting logic. This sorting implementation is identical to the one in 🤖 Prompt for AI Agents |
||
| return diagnostics; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the admission example self-contained.
The new call introduces
candidateandnowwithout defining them. Add a minimalTestRunCandidatefixture and an explicit millisecond-precision UTC timestamp, or clearly mark them as placeholders so consumers do not copy a non-compiling example.🤖 Prompt for AI Agents