Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions npm/marquette/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ deployment tooling can bind a `tests` check to retained, immutable facts:
import { defineTestRunEvidence } from "@vizejs/marquette/test-run";
import { validateTestRunEvidence } from "@vizejs/marquette/test-run/validate";
import { testRunAdmissionId } from "@vizejs/marquette/test-run/canonical";
import { admitTestRun } from "@vizejs/marquette/test-run/admission";

const diagnostics = validateTestRunEvidence(evidence);
const admissionId = await testRunAdmissionId(evidence); // test-run:<sha256>
const rejections = await admitTestRun(evidence, candidate, admissionId, now);
Comment on lines +102 to +106

Copy link
Copy Markdown

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 candidate and now without defining them. Add a minimal TestRunCandidate fixture 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/marquette/README.md` around lines 102 - 106, Make the admission example
self-contained by defining a minimal TestRunCandidate fixture for candidate and
an explicit UTC timestamp with millisecond precision for now before the
admitTestRun call. Keep the existing validation, admission ID, and rejection
flow unchanged.

```

Validation shares its `VIZE_MARQUETTE_1xx` codes, paths, and ordering with the
Expand Down
5 changes: 5 additions & 0 deletions npm/marquette/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
"import": "./dist/test-run-canonical.mjs",
"default": "./dist/test-run-canonical.mjs"
},
"./test-run/admission": {
"types": "./dist/test-run-admission.d.mts",
"import": "./dist/test-run-admission.mjs",
"default": "./dist/test-run-admission.mjs"
},
"./schema": "./dist/application-contract.schema.json",
"./test-run/schema": "./dist/test-run-evidence.schema.json"
},
Expand Down
5 changes: 5 additions & 0 deletions npm/marquette/scripts/check-size.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const budgets = [
file: "dist/test-run-canonical.mjs",
maximumGzipBytes: 2048,
},
{
entry: "@vizejs/marquette/test-run/admission",
file: "dist/test-run-admission.mjs",
maximumGzipBytes: 2048,
},
];

for (const { entry, file, maximumGzipBytes } of budgets) {
Expand Down
105 changes: 105 additions & 0 deletions npm/marquette/src/test-run-admission.test.ts
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",
]);
});
143 changes: 143 additions & 0 deletions npm/marquette/src/test-run-admission.ts
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 validateTestRunEvidence (as seen in test-run-validate.ts). Consider extracting it into a shared helper function (e.g., sortDiagnostics) within test-run-validate-rules.ts or validate.ts to DRY up the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/marquette/src/test-run-admission.ts` around lines 127 - 141, Extract the
repeated diagnostic comparator from this sorting call and
validateTestRunEvidence into a shared sortDiagnostics helper in
test-run-validate-rules.ts or validate.ts. Update both callers to reuse the
helper while preserving ordering by path, then code, then message.

return diagnostics;
}
1 change: 1 addition & 0 deletions npm/marquette/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default defineConfig({
"src/test-run.ts",
"src/test-run-validate.ts",
"src/test-run-canonical.ts",
"src/test-run-admission.ts",
],
format: "esm",
dts: true,
Expand Down
Loading