@vizejs/marquette 0.302.0 → 0.303.0
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.
- package/LICENSE +21 -0
- package/dist/test-run-admission.d.mts +58 -2
- package/dist/test-run-admission.d.mts.map +1 -1
- package/dist/test-run-admission.mjs +100 -2
- package/dist/test-run-admission.mjs.map +1 -1
- package/dist/test-run-admission.schema.json +84 -0
- package/dist/test-run-canonical-CB-KzZxk.d.mts +35 -0
- package/dist/test-run-canonical-CB-KzZxk.d.mts.map +1 -0
- package/dist/test-run-canonical.d.mts +2 -35
- package/dist/test-run-check.d.mts +80 -0
- package/dist/test-run-check.d.mts.map +1 -0
- package/dist/test-run-check.mjs +116 -0
- package/dist/test-run-check.mjs.map +1 -0
- package/dist/test-run-check.schema.json +82 -0
- package/dist/test-run-transition.d.mts +146 -0
- package/dist/test-run-transition.d.mts.map +1 -0
- package/dist/test-run-transition.mjs +188 -0
- package/dist/test-run-transition.mjs.map +1 -0
- package/dist/test-run-transition.schema.json +148 -0
- package/dist/{test-run-validate-XF0vyoWI.mjs → test-run-validate-C_KR031E.mjs} +5 -5
- package/dist/test-run-validate-C_KR031E.mjs.map +1 -0
- package/dist/test-run-validate.mjs +1 -1
- package/package.json +12 -12
- package/dist/test-run-canonical.d.mts.map +0 -1
- package/dist/test-run-validate-XF0vyoWI.mjs.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ubugeeei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as TestRunEvidence } from "./test-run-model-DkllmEsY.mjs";
|
|
2
2
|
import { MarquetteDiagnostic } from "./validate.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { r as parseTestRunAdmissionId, t as TEST_RUN_ADMISSION_PREFIX } from "./test-run-canonical-CB-KzZxk.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/test-run-admission.d.ts
|
|
6
6
|
/**
|
|
@@ -43,6 +43,62 @@ interface TestRunCandidate {
|
|
|
43
43
|
* hand the parsed record here.
|
|
44
44
|
*/
|
|
45
45
|
declare function admitTestRun(evidence: TestRunEvidence, candidate: TestRunCandidate, admissionId: string, now: string): Promise<MarquetteDiagnostic[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Every denial code, in the stable lexicographic decision order.
|
|
48
|
+
*
|
|
49
|
+
* The vocabulary is shared by every backend family: a JavaScript, Rust, Go,
|
|
50
|
+
* or JVM host must derive the same codes from the same diagnostics, as
|
|
51
|
+
* pinned by the shared `tests/fixtures/test-run-evidence` decision fixtures.
|
|
52
|
+
* Codes are append-only: they are never renamed, renumbered, reused, or
|
|
53
|
+
* removed, and a new rejection cause always ships with a new code.
|
|
54
|
+
*/
|
|
55
|
+
declare const TEST_RUN_DENIAL_CODES: readonly ["admission-id-malformed", "admission-id-mismatch", "admission-time-malformed", "candidate-application-mismatch", "candidate-artifact-fingerprint-mismatch", "candidate-contract-fingerprint-mismatch", "candidate-environment-mismatch", "candidate-release-mismatch", "candidate-source-revision-mismatch", "check-candidate-mismatch", "check-invalid", "check-observer-not-independent", "record-expired", "record-invalid", "skipped-tests-recorded", "transition-chain-broken", "transition-invalid", "transition-replayed", "transition-state-mismatch", "verification-not-accepted"];
|
|
56
|
+
/** Stable machine-readable cause class of one admission denial. */
|
|
57
|
+
type TestRunDenialCode = (typeof TEST_RUN_DENIAL_CODES)[number];
|
|
58
|
+
/**
|
|
59
|
+
* Structured allow-or-deny admission decision for one exact candidate.
|
|
60
|
+
*
|
|
61
|
+
* The decision carries the machine-readable cause classes next to the exact
|
|
62
|
+
* diagnostics, so a deployment gate in any language can act on one bounded
|
|
63
|
+
* vocabulary while operators keep the full explanation. Serialization
|
|
64
|
+
* follows the shared `test-run-admission` schema; decisions are outputs, so
|
|
65
|
+
* a gate must never trust a decision it did not compute itself.
|
|
66
|
+
*/
|
|
67
|
+
interface TestRunAdmissionDecision {
|
|
68
|
+
/** Whether the record admits the candidate; true only with no diagnostics. */
|
|
69
|
+
readonly allowed: boolean;
|
|
70
|
+
/** Deduplicated denial causes sorted lexicographically; empty when allowed. */
|
|
71
|
+
readonly denialCodes: readonly TestRunDenialCode[];
|
|
72
|
+
/** Complete diagnostics in the stable path, code, message order. */
|
|
73
|
+
readonly diagnostics: readonly MarquetteDiagnostic[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the stable denial code one admission diagnostic maps to.
|
|
77
|
+
*
|
|
78
|
+
* The mapping is total and identical in every host family: admission codes
|
|
79
|
+
* `VIZE_MARQUETTE_141` through `VIZE_MARQUETTE_148` map to their exact
|
|
80
|
+
* cause, `VIZE_MARQUETTE_144` distinguishes the mismatched candidate binding
|
|
81
|
+
* by its diagnostic path, `VIZE_MARQUETTE_149` through `VIZE_MARQUETTE_151`
|
|
82
|
+
* map to their tests-check cause, `VIZE_MARQUETTE_156` through
|
|
83
|
+
* `VIZE_MARQUETTE_159` map to their transition cause, every other
|
|
84
|
+
* diagnostic at a `check.` path is a `check-invalid` tests-check validation
|
|
85
|
+
* failure, every other diagnostic at a `transition.` path is a
|
|
86
|
+
* `transition-invalid` transition validation failure, and every remaining
|
|
87
|
+
* diagnostic is a `record-invalid` record-validation failure.
|
|
88
|
+
*/
|
|
89
|
+
declare function testRunDenialCode(diagnostic: MarquetteDiagnostic): TestRunDenialCode;
|
|
90
|
+
/**
|
|
91
|
+
* Decides one candidate and returns the structured admission decision.
|
|
92
|
+
*
|
|
93
|
+
* The decision wraps {@link admitTestRun}: `diagnostics` is exactly its
|
|
94
|
+
* result, `denialCodes` maps every diagnostic through
|
|
95
|
+
* {@link testRunDenialCode} and then deduplicates and sorts the codes
|
|
96
|
+
* lexicographically, and `allowed` is true only when both are empty. Codes,
|
|
97
|
+
* ordering, and diagnostics are identical to the native implementation, as
|
|
98
|
+
* pinned by the shared decision fixtures. Inputs carry the same obligations
|
|
99
|
+
* as {@link admitTestRun}.
|
|
100
|
+
*/
|
|
101
|
+
declare function decideTestRunAdmission(evidence: TestRunEvidence, candidate: TestRunCandidate, admissionId: string, now: string): Promise<TestRunAdmissionDecision>;
|
|
46
102
|
//#endregion
|
|
47
|
-
export { TEST_RUN_ADMISSION_PREFIX, TestRunCandidate, admitTestRun, parseTestRunAdmissionId };
|
|
103
|
+
export { TEST_RUN_ADMISSION_PREFIX, TEST_RUN_DENIAL_CODES, TestRunAdmissionDecision, TestRunCandidate, TestRunDenialCode, admitTestRun, decideTestRunAdmission, parseTestRunAdmissionId, testRunDenialCode };
|
|
48
104
|
//# sourceMappingURL=test-run-admission.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-run-admission.d.mts","names":[],"sources":["../src/test-run-admission.ts"],"mappings":";;;;;;;AAcA;;;;UAAiB,gBAAA;EAIN;EAAA,SAFA,WAAA;EAMA;EAAA,SAJA,WAAA;EAQA;EAAA,SANA,mBAAA;EAMmB;EAAA,SAJnB,cAAA;EA0BuB;EAAA,SAxBvB,OAAA;EAyBC;EAAA,SAvBD,mBAAA;AAAA
|
|
1
|
+
{"version":3,"file":"test-run-admission.d.mts","names":[],"sources":["../src/test-run-admission.ts"],"mappings":";;;;;;;AAcA;;;;UAAiB,gBAAA;EAIN;EAAA,SAFA,WAAA;EAMA;EAAA,SAJA,WAAA;EAQA;EAAA,SANA,mBAAA;EAMmB;EAAA,SAJnB,cAAA;EA0BuB;EAAA,SAxBvB,OAAA;EAyBC;EAAA,SAvBD,mBAAA;AAAA;;;;;;;;;;;;;;AA+HX;;;;;AAwBA;iBAjIsB,YAAA,CACpB,QAAA,EAAU,eAAA,EACV,SAAA,EAAW,gBAAA,EACX,WAAA,UACA,GAAA,WACC,OAAA,CAAQ,mBAAA;;;;AAuIX;;;;;;cAnCa,qBAAA;;KAwBD,iBAAA,WAA4B,qBAAA;;;AA2CxC;;;;;;;UAhCiB,wBAAA;EAgCoE;EAAA,SA9B1E,OAAA;EAqFiC;EAAA,SAnFjC,WAAA,WAAsB,iBAAA;EAoFrB;EAAA,SAlFD,WAAA,WAAsB,mBAAA;AAAA;;;;;;;;;;;;;;;iBA0BjB,iBAAA,CAAkB,UAAA,EAAY,mBAAA,GAAsB,iBAAA;;;;;;;;;;;;iBAuD9C,sBAAA,CACpB,QAAA,EAAU,eAAA,EACV,SAAA,EAAW,gBAAA,EACX,WAAA,UACA,GAAA,WACC,OAAA,CAAQ,wBAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as isStrictTimestamp, t as validateTestRunEvidence, u as error } from "./test-run-validate-C_KR031E.mjs";
|
|
2
2
|
import { TEST_RUN_ADMISSION_PREFIX, parseTestRunAdmissionId, testRunFingerprint } from "./test-run-canonical.mjs";
|
|
3
3
|
//#region src/test-run-admission.ts
|
|
4
4
|
/**
|
|
@@ -66,7 +66,105 @@ async function admitTestRun(evidence, candidate, admissionId, now) {
|
|
|
66
66
|
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);
|
|
67
67
|
return diagnostics;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Every denial code, in the stable lexicographic decision order.
|
|
71
|
+
*
|
|
72
|
+
* The vocabulary is shared by every backend family: a JavaScript, Rust, Go,
|
|
73
|
+
* or JVM host must derive the same codes from the same diagnostics, as
|
|
74
|
+
* pinned by the shared `tests/fixtures/test-run-evidence` decision fixtures.
|
|
75
|
+
* Codes are append-only: they are never renamed, renumbered, reused, or
|
|
76
|
+
* removed, and a new rejection cause always ships with a new code.
|
|
77
|
+
*/
|
|
78
|
+
const TEST_RUN_DENIAL_CODES = [
|
|
79
|
+
"admission-id-malformed",
|
|
80
|
+
"admission-id-mismatch",
|
|
81
|
+
"admission-time-malformed",
|
|
82
|
+
"candidate-application-mismatch",
|
|
83
|
+
"candidate-artifact-fingerprint-mismatch",
|
|
84
|
+
"candidate-contract-fingerprint-mismatch",
|
|
85
|
+
"candidate-environment-mismatch",
|
|
86
|
+
"candidate-release-mismatch",
|
|
87
|
+
"candidate-source-revision-mismatch",
|
|
88
|
+
"check-candidate-mismatch",
|
|
89
|
+
"check-invalid",
|
|
90
|
+
"check-observer-not-independent",
|
|
91
|
+
"record-expired",
|
|
92
|
+
"record-invalid",
|
|
93
|
+
"skipped-tests-recorded",
|
|
94
|
+
"transition-chain-broken",
|
|
95
|
+
"transition-invalid",
|
|
96
|
+
"transition-replayed",
|
|
97
|
+
"transition-state-mismatch",
|
|
98
|
+
"verification-not-accepted"
|
|
99
|
+
];
|
|
100
|
+
const CANDIDATE_MISMATCH_CODES = new Map([
|
|
101
|
+
["application", "candidate-application-mismatch"],
|
|
102
|
+
["artifact.fingerprint", "candidate-artifact-fingerprint-mismatch"],
|
|
103
|
+
["contractFingerprint", "candidate-contract-fingerprint-mismatch"],
|
|
104
|
+
["environment", "candidate-environment-mismatch"],
|
|
105
|
+
["release", "candidate-release-mismatch"],
|
|
106
|
+
["sourceRevision", "candidate-source-revision-mismatch"]
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* Returns the stable denial code one admission diagnostic maps to.
|
|
110
|
+
*
|
|
111
|
+
* The mapping is total and identical in every host family: admission codes
|
|
112
|
+
* `VIZE_MARQUETTE_141` through `VIZE_MARQUETTE_148` map to their exact
|
|
113
|
+
* cause, `VIZE_MARQUETTE_144` distinguishes the mismatched candidate binding
|
|
114
|
+
* by its diagnostic path, `VIZE_MARQUETTE_149` through `VIZE_MARQUETTE_151`
|
|
115
|
+
* map to their tests-check cause, `VIZE_MARQUETTE_156` through
|
|
116
|
+
* `VIZE_MARQUETTE_159` map to their transition cause, every other
|
|
117
|
+
* diagnostic at a `check.` path is a `check-invalid` tests-check validation
|
|
118
|
+
* failure, every other diagnostic at a `transition.` path is a
|
|
119
|
+
* `transition-invalid` transition validation failure, and every remaining
|
|
120
|
+
* diagnostic is a `record-invalid` record-validation failure.
|
|
121
|
+
*/
|
|
122
|
+
function testRunDenialCode(diagnostic) {
|
|
123
|
+
switch (diagnostic.code) {
|
|
124
|
+
case "VIZE_MARQUETTE_141": return "admission-id-malformed";
|
|
125
|
+
case "VIZE_MARQUETTE_142": return "admission-id-mismatch";
|
|
126
|
+
case "VIZE_MARQUETTE_144": {
|
|
127
|
+
const mismatch = CANDIDATE_MISMATCH_CODES.get(diagnostic.path);
|
|
128
|
+
if (mismatch !== void 0) return mismatch;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case "VIZE_MARQUETTE_145": return "record-expired";
|
|
132
|
+
case "VIZE_MARQUETTE_146": return "verification-not-accepted";
|
|
133
|
+
case "VIZE_MARQUETTE_147": return "skipped-tests-recorded";
|
|
134
|
+
case "VIZE_MARQUETTE_148": return "admission-time-malformed";
|
|
135
|
+
case "VIZE_MARQUETTE_149": return "check-candidate-mismatch";
|
|
136
|
+
case "VIZE_MARQUETTE_150": return "check-invalid";
|
|
137
|
+
case "VIZE_MARQUETTE_151": return "check-observer-not-independent";
|
|
138
|
+
case "VIZE_MARQUETTE_156": return "transition-state-mismatch";
|
|
139
|
+
case "VIZE_MARQUETTE_157": return "transition-chain-broken";
|
|
140
|
+
case "VIZE_MARQUETTE_158": return "transition-replayed";
|
|
141
|
+
case "VIZE_MARQUETTE_159": return "transition-state-mismatch";
|
|
142
|
+
default: break;
|
|
143
|
+
}
|
|
144
|
+
if (diagnostic.path.startsWith("check.")) return "check-invalid";
|
|
145
|
+
return diagnostic.path.startsWith("transition.") ? "transition-invalid" : "record-invalid";
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Decides one candidate and returns the structured admission decision.
|
|
149
|
+
*
|
|
150
|
+
* The decision wraps {@link admitTestRun}: `diagnostics` is exactly its
|
|
151
|
+
* result, `denialCodes` maps every diagnostic through
|
|
152
|
+
* {@link testRunDenialCode} and then deduplicates and sorts the codes
|
|
153
|
+
* lexicographically, and `allowed` is true only when both are empty. Codes,
|
|
154
|
+
* ordering, and diagnostics are identical to the native implementation, as
|
|
155
|
+
* pinned by the shared decision fixtures. Inputs carry the same obligations
|
|
156
|
+
* as {@link admitTestRun}.
|
|
157
|
+
*/
|
|
158
|
+
async function decideTestRunAdmission(evidence, candidate, admissionId, now) {
|
|
159
|
+
const diagnostics = await admitTestRun(evidence, candidate, admissionId, now);
|
|
160
|
+
const denialCodes = [...new Set(diagnostics.map(testRunDenialCode))].sort();
|
|
161
|
+
return {
|
|
162
|
+
allowed: diagnostics.length === 0,
|
|
163
|
+
denialCodes,
|
|
164
|
+
diagnostics
|
|
165
|
+
};
|
|
166
|
+
}
|
|
69
167
|
//#endregion
|
|
70
|
-
export { TEST_RUN_ADMISSION_PREFIX, admitTestRun, parseTestRunAdmissionId };
|
|
168
|
+
export { TEST_RUN_ADMISSION_PREFIX, TEST_RUN_DENIAL_CODES, admitTestRun, decideTestRunAdmission, parseTestRunAdmissionId, testRunDenialCode };
|
|
71
169
|
|
|
72
170
|
//# sourceMappingURL=test-run-admission.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-run-admission.mjs","names":[],"sources":["../src/test-run-admission.ts"],"sourcesContent":["import type { TestRunEvidence } from \"./test-run-model.js\";\nimport type { MarquetteDiagnostic } from \"./validate.js\";\nimport { parseTestRunAdmissionId, testRunFingerprint } from \"./test-run-canonical.js\";\nimport { validateTestRunEvidence } from \"./test-run-validate.js\";\nimport { error, isStrictTimestamp } from \"./test-run-validate-rules.js\";\n\nexport { TEST_RUN_ADMISSION_PREFIX, parseTestRunAdmissionId } from \"./test-run-canonical.js\";\n\n/**\n * Exact release candidate a deployment gate wants evidence for.\n *\n * Every field must match the record exactly; admission never falls back to a\n * newer, older, or partially matching record.\n */\nexport interface TestRunCandidate {\n /** Application the gate is deploying. */\n readonly application: string;\n /** Deployment environment the gate is promoting into. */\n readonly environment: string;\n /** Lowercase SHA-256 fingerprint of the application contract. */\n readonly contractFingerprint: string;\n /** Exact source revision of the candidate. */\n readonly sourceRevision: string;\n /** Release the candidate belongs to. */\n readonly release: string;\n /** Lowercase SHA-256 fingerprint of the exact artifact being promoted. */\n readonly artifactFingerprint: string;\n}\n\n/**\n * Decides whether one record admits one exact candidate at one instant.\n *\n * An empty result admits the deployment. Any diagnostic rejects it: the\n * record must validate cleanly, its canonical fingerprint must be the one\n * named by `admissionId`, every candidate binding must match exactly, the\n * record must not be expired at `now`, the independent verification must\n * have accepted the run, and no skipped test may remain unaccounted for.\n *\n * Codes, paths, messages, and ordering are identical to the native\n * implementation, so both host families reach the same decision for the\n * same record.\n *\n * `now` must be a millisecond-precision UTC timestamp such as\n * `2026-01-01T00:00:00.000Z`; the fixed-width format keeps the expiry\n * comparison exact. Callers own retrieval: fetch the canonical bytes from an\n * immutable store within their own deadline, refuse oversized content, and\n * hand the parsed record here.\n */\nexport async function admitTestRun(\n evidence: TestRunEvidence,\n candidate: TestRunCandidate,\n admissionId: string,\n now: string,\n): Promise<MarquetteDiagnostic[]> {\n const diagnostics = validateTestRunEvidence(evidence);\n\n const nowIsExact = isStrictTimestamp(now);\n if (!nowIsExact) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_148\",\n \"admission.now\",\n \"admission time must be a millisecond-precision UTC instant\",\n ),\n );\n }\n\n const expected = parseTestRunAdmissionId(admissionId);\n if (expected === undefined) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_141\",\n \"admission.id\",\n \"admission id must be test-run: followed by 64 lowercase hexadecimal characters\",\n ),\n );\n } else if ((await testRunFingerprint(evidence)) !== expected) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_142\",\n \"admission.id\",\n \"admission id does not name this record's canonical fingerprint\",\n ),\n );\n }\n\n const bindings = [\n [evidence.application, candidate.application, \"application\"],\n [evidence.environment, candidate.environment, \"environment\"],\n [evidence.contractFingerprint, candidate.contractFingerprint, \"contractFingerprint\"],\n [evidence.sourceRevision, candidate.sourceRevision, \"sourceRevision\"],\n [evidence.release, candidate.release, \"release\"],\n [evidence.artifact.fingerprint, candidate.artifactFingerprint, \"artifact.fingerprint\"],\n ] as const;\n for (const [recorded, wanted, field] of bindings) {\n if (recorded !== wanted) {\n diagnostics.push(\n error(\"VIZE_MARQUETTE_144\", field, `record does not bind the candidate ${field}`),\n );\n }\n }\n\n if (nowIsExact && evidence.validUntil <= now) {\n diagnostics.push(\n error(\"VIZE_MARQUETTE_145\", \"validUntil\", \"record is expired at the admission time\"),\n );\n }\n if (evidence.verification.outcome !== \"accepted\") {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_146\",\n \"verification.outcome\",\n \"only an accepted verification can admit a deployment\",\n ),\n );\n }\n if (evidence.verification.skipped > 0) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_147\",\n \"verification.skipped\",\n \"skipped tests are not approved for deployment admission\",\n ),\n );\n }\n\n diagnostics.sort((left, right) =>\n left.path !== right.path\n ? left.path < right.path\n ? -1\n : 1\n : left.code !== right.code\n ? left.code < right.code\n ? -1\n : 1\n : left.message < right.message\n ? -1\n : left.message > right.message\n ? 1\n : 0,\n );\n return diagnostics;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgDA,eAAsB,aACpB,UACA,WACA,aACA,KACgC;CAChC,MAAM,cAAc,wBAAwB,SAAS;CAErD,MAAM,aAAa,kBAAkB,IAAI;CACzC,IAAI,CAAC,YACH,YAAY,KACV,MACE,sBACA,iBACA,6DACD,CACF;CAGH,MAAM,WAAW,wBAAwB,YAAY;CACrD,IAAI,aAAa,KAAA,GACf,YAAY,KACV,MACE,sBACA,gBACA,iFACD,CACF;MACI,IAAK,MAAM,mBAAmB,SAAS,KAAM,UAClD,YAAY,KACV,MACE,sBACA,gBACA,iEACD,CACF;CAGH,MAAM,WAAW;EACf;GAAC,SAAS;GAAa,UAAU;GAAa;GAAc;EAC5D;GAAC,SAAS;GAAa,UAAU;GAAa;GAAc;EAC5D;GAAC,SAAS;GAAqB,UAAU;GAAqB;GAAsB;EACpF;GAAC,SAAS;GAAgB,UAAU;GAAgB;GAAiB;EACrE;GAAC,SAAS;GAAS,UAAU;GAAS;GAAU;EAChD;GAAC,SAAS,SAAS;GAAa,UAAU;GAAqB;GAAuB;EACvF;CACD,KAAK,MAAM,CAAC,UAAU,QAAQ,UAAU,UACtC,IAAI,aAAa,QACf,YAAY,KACV,MAAM,sBAAsB,OAAO,sCAAsC,QAAQ,CAClF;CAIL,IAAI,cAAc,SAAS,cAAc,KACvC,YAAY,KACV,MAAM,sBAAsB,cAAc,0CAA0C,CACrF;CAEH,IAAI,SAAS,aAAa,YAAY,YACpC,YAAY,KACV,MACE,sBACA,wBACA,uDACD,CACF;CAEH,IAAI,SAAS,aAAa,UAAU,GAClC,YAAY,KACV,MACE,sBACA,wBACA,0DACD,CACF;CAGH,YAAY,MAAM,MAAM,UACtB,KAAK,SAAS,MAAM,OAChB,KAAK,OAAO,MAAM,OAChB,KACA,IACF,KAAK,SAAS,MAAM,OAClB,KAAK,OAAO,MAAM,OAChB,KACA,IACF,KAAK,UAAU,MAAM,UACnB,KACA,KAAK,UAAU,MAAM,UACnB,IACA,EACX;CACD,OAAO"}
|
|
1
|
+
{"version":3,"file":"test-run-admission.mjs","names":[],"sources":["../src/test-run-admission.ts"],"sourcesContent":["import type { TestRunEvidence } from \"./test-run-model.js\";\nimport type { MarquetteDiagnostic } from \"./validate.js\";\nimport { parseTestRunAdmissionId, testRunFingerprint } from \"./test-run-canonical.js\";\nimport { validateTestRunEvidence } from \"./test-run-validate.js\";\nimport { error, isStrictTimestamp } from \"./test-run-validate-rules.js\";\n\nexport { TEST_RUN_ADMISSION_PREFIX, parseTestRunAdmissionId } from \"./test-run-canonical.js\";\n\n/**\n * Exact release candidate a deployment gate wants evidence for.\n *\n * Every field must match the record exactly; admission never falls back to a\n * newer, older, or partially matching record.\n */\nexport interface TestRunCandidate {\n /** Application the gate is deploying. */\n readonly application: string;\n /** Deployment environment the gate is promoting into. */\n readonly environment: string;\n /** Lowercase SHA-256 fingerprint of the application contract. */\n readonly contractFingerprint: string;\n /** Exact source revision of the candidate. */\n readonly sourceRevision: string;\n /** Release the candidate belongs to. */\n readonly release: string;\n /** Lowercase SHA-256 fingerprint of the exact artifact being promoted. */\n readonly artifactFingerprint: string;\n}\n\n/**\n * Decides whether one record admits one exact candidate at one instant.\n *\n * An empty result admits the deployment. Any diagnostic rejects it: the\n * record must validate cleanly, its canonical fingerprint must be the one\n * named by `admissionId`, every candidate binding must match exactly, the\n * record must not be expired at `now`, the independent verification must\n * have accepted the run, and no skipped test may remain unaccounted for.\n *\n * Codes, paths, messages, and ordering are identical to the native\n * implementation, so both host families reach the same decision for the\n * same record.\n *\n * `now` must be a millisecond-precision UTC timestamp such as\n * `2026-01-01T00:00:00.000Z`; the fixed-width format keeps the expiry\n * comparison exact. Callers own retrieval: fetch the canonical bytes from an\n * immutable store within their own deadline, refuse oversized content, and\n * hand the parsed record here.\n */\nexport async function admitTestRun(\n evidence: TestRunEvidence,\n candidate: TestRunCandidate,\n admissionId: string,\n now: string,\n): Promise<MarquetteDiagnostic[]> {\n const diagnostics = validateTestRunEvidence(evidence);\n\n const nowIsExact = isStrictTimestamp(now);\n if (!nowIsExact) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_148\",\n \"admission.now\",\n \"admission time must be a millisecond-precision UTC instant\",\n ),\n );\n }\n\n const expected = parseTestRunAdmissionId(admissionId);\n if (expected === undefined) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_141\",\n \"admission.id\",\n \"admission id must be test-run: followed by 64 lowercase hexadecimal characters\",\n ),\n );\n } else if ((await testRunFingerprint(evidence)) !== expected) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_142\",\n \"admission.id\",\n \"admission id does not name this record's canonical fingerprint\",\n ),\n );\n }\n\n const bindings = [\n [evidence.application, candidate.application, \"application\"],\n [evidence.environment, candidate.environment, \"environment\"],\n [evidence.contractFingerprint, candidate.contractFingerprint, \"contractFingerprint\"],\n [evidence.sourceRevision, candidate.sourceRevision, \"sourceRevision\"],\n [evidence.release, candidate.release, \"release\"],\n [evidence.artifact.fingerprint, candidate.artifactFingerprint, \"artifact.fingerprint\"],\n ] as const;\n for (const [recorded, wanted, field] of bindings) {\n if (recorded !== wanted) {\n diagnostics.push(\n error(\"VIZE_MARQUETTE_144\", field, `record does not bind the candidate ${field}`),\n );\n }\n }\n\n if (nowIsExact && evidence.validUntil <= now) {\n diagnostics.push(\n error(\"VIZE_MARQUETTE_145\", \"validUntil\", \"record is expired at the admission time\"),\n );\n }\n if (evidence.verification.outcome !== \"accepted\") {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_146\",\n \"verification.outcome\",\n \"only an accepted verification can admit a deployment\",\n ),\n );\n }\n if (evidence.verification.skipped > 0) {\n diagnostics.push(\n error(\n \"VIZE_MARQUETTE_147\",\n \"verification.skipped\",\n \"skipped tests are not approved for deployment admission\",\n ),\n );\n }\n\n diagnostics.sort((left, right) =>\n left.path !== right.path\n ? left.path < right.path\n ? -1\n : 1\n : left.code !== right.code\n ? left.code < right.code\n ? -1\n : 1\n : left.message < right.message\n ? -1\n : left.message > right.message\n ? 1\n : 0,\n );\n return diagnostics;\n}\n\n/**\n * Every denial code, in the stable lexicographic decision order.\n *\n * The vocabulary is shared by every backend family: a JavaScript, Rust, Go,\n * or JVM host must derive the same codes from the same diagnostics, as\n * pinned by the shared `tests/fixtures/test-run-evidence` decision fixtures.\n * Codes are append-only: they are never renamed, renumbered, reused, or\n * removed, and a new rejection cause always ships with a new code.\n */\nexport const TEST_RUN_DENIAL_CODES = [\n \"admission-id-malformed\",\n \"admission-id-mismatch\",\n \"admission-time-malformed\",\n \"candidate-application-mismatch\",\n \"candidate-artifact-fingerprint-mismatch\",\n \"candidate-contract-fingerprint-mismatch\",\n \"candidate-environment-mismatch\",\n \"candidate-release-mismatch\",\n \"candidate-source-revision-mismatch\",\n \"check-candidate-mismatch\",\n \"check-invalid\",\n \"check-observer-not-independent\",\n \"record-expired\",\n \"record-invalid\",\n \"skipped-tests-recorded\",\n \"transition-chain-broken\",\n \"transition-invalid\",\n \"transition-replayed\",\n \"transition-state-mismatch\",\n \"verification-not-accepted\",\n] as const;\n\n/** Stable machine-readable cause class of one admission denial. */\nexport type TestRunDenialCode = (typeof TEST_RUN_DENIAL_CODES)[number];\n\n/**\n * Structured allow-or-deny admission decision for one exact candidate.\n *\n * The decision carries the machine-readable cause classes next to the exact\n * diagnostics, so a deployment gate in any language can act on one bounded\n * vocabulary while operators keep the full explanation. Serialization\n * follows the shared `test-run-admission` schema; decisions are outputs, so\n * a gate must never trust a decision it did not compute itself.\n */\nexport interface TestRunAdmissionDecision {\n /** Whether the record admits the candidate; true only with no diagnostics. */\n readonly allowed: boolean;\n /** Deduplicated denial causes sorted lexicographically; empty when allowed. */\n readonly denialCodes: readonly TestRunDenialCode[];\n /** Complete diagnostics in the stable path, code, message order. */\n readonly diagnostics: readonly MarquetteDiagnostic[];\n}\n\nconst CANDIDATE_MISMATCH_CODES: ReadonlyMap<string, TestRunDenialCode> = new Map([\n [\"application\", \"candidate-application-mismatch\"],\n [\"artifact.fingerprint\", \"candidate-artifact-fingerprint-mismatch\"],\n [\"contractFingerprint\", \"candidate-contract-fingerprint-mismatch\"],\n [\"environment\", \"candidate-environment-mismatch\"],\n [\"release\", \"candidate-release-mismatch\"],\n [\"sourceRevision\", \"candidate-source-revision-mismatch\"],\n]);\n\n/**\n * Returns the stable denial code one admission diagnostic maps to.\n *\n * The mapping is total and identical in every host family: admission codes\n * `VIZE_MARQUETTE_141` through `VIZE_MARQUETTE_148` map to their exact\n * cause, `VIZE_MARQUETTE_144` distinguishes the mismatched candidate binding\n * by its diagnostic path, `VIZE_MARQUETTE_149` through `VIZE_MARQUETTE_151`\n * map to their tests-check cause, `VIZE_MARQUETTE_156` through\n * `VIZE_MARQUETTE_159` map to their transition cause, every other\n * diagnostic at a `check.` path is a `check-invalid` tests-check validation\n * failure, every other diagnostic at a `transition.` path is a\n * `transition-invalid` transition validation failure, and every remaining\n * diagnostic is a `record-invalid` record-validation failure.\n */\nexport function testRunDenialCode(diagnostic: MarquetteDiagnostic): TestRunDenialCode {\n switch (diagnostic.code) {\n case \"VIZE_MARQUETTE_141\":\n return \"admission-id-malformed\";\n case \"VIZE_MARQUETTE_142\":\n return \"admission-id-mismatch\";\n case \"VIZE_MARQUETTE_144\": {\n const mismatch = CANDIDATE_MISMATCH_CODES.get(diagnostic.path);\n if (mismatch !== undefined) {\n return mismatch;\n }\n break;\n }\n case \"VIZE_MARQUETTE_145\":\n return \"record-expired\";\n case \"VIZE_MARQUETTE_146\":\n return \"verification-not-accepted\";\n case \"VIZE_MARQUETTE_147\":\n return \"skipped-tests-recorded\";\n case \"VIZE_MARQUETTE_148\":\n return \"admission-time-malformed\";\n case \"VIZE_MARQUETTE_149\":\n return \"check-candidate-mismatch\";\n case \"VIZE_MARQUETTE_150\":\n return \"check-invalid\";\n case \"VIZE_MARQUETTE_151\":\n return \"check-observer-not-independent\";\n case \"VIZE_MARQUETTE_156\":\n return \"transition-state-mismatch\";\n case \"VIZE_MARQUETTE_157\":\n return \"transition-chain-broken\";\n case \"VIZE_MARQUETTE_158\":\n return \"transition-replayed\";\n case \"VIZE_MARQUETTE_159\":\n return \"transition-state-mismatch\";\n default:\n break;\n }\n if (diagnostic.path.startsWith(\"check.\")) {\n return \"check-invalid\";\n }\n return diagnostic.path.startsWith(\"transition.\") ? \"transition-invalid\" : \"record-invalid\";\n}\n\n/**\n * Decides one candidate and returns the structured admission decision.\n *\n * The decision wraps {@link admitTestRun}: `diagnostics` is exactly its\n * result, `denialCodes` maps every diagnostic through\n * {@link testRunDenialCode} and then deduplicates and sorts the codes\n * lexicographically, and `allowed` is true only when both are empty. Codes,\n * ordering, and diagnostics are identical to the native implementation, as\n * pinned by the shared decision fixtures. Inputs carry the same obligations\n * as {@link admitTestRun}.\n */\nexport async function decideTestRunAdmission(\n evidence: TestRunEvidence,\n candidate: TestRunCandidate,\n admissionId: string,\n now: string,\n): Promise<TestRunAdmissionDecision> {\n const diagnostics = await admitTestRun(evidence, candidate, admissionId, now);\n const denialCodes = [...new Set(diagnostics.map(testRunDenialCode))].sort();\n return { allowed: diagnostics.length === 0, denialCodes, diagnostics };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgDA,eAAsB,aACpB,UACA,WACA,aACA,KACgC;CAChC,MAAM,cAAc,wBAAwB,SAAS;CAErD,MAAM,aAAa,kBAAkB,IAAI;CACzC,IAAI,CAAC,YACH,YAAY,KACV,MACE,sBACA,iBACA,6DACD,CACF;CAGH,MAAM,WAAW,wBAAwB,YAAY;CACrD,IAAI,aAAa,KAAA,GACf,YAAY,KACV,MACE,sBACA,gBACA,iFACD,CACF;MACI,IAAK,MAAM,mBAAmB,SAAS,KAAM,UAClD,YAAY,KACV,MACE,sBACA,gBACA,iEACD,CACF;CAGH,MAAM,WAAW;EACf;GAAC,SAAS;GAAa,UAAU;GAAa;GAAc;EAC5D;GAAC,SAAS;GAAa,UAAU;GAAa;GAAc;EAC5D;GAAC,SAAS;GAAqB,UAAU;GAAqB;GAAsB;EACpF;GAAC,SAAS;GAAgB,UAAU;GAAgB;GAAiB;EACrE;GAAC,SAAS;GAAS,UAAU;GAAS;GAAU;EAChD;GAAC,SAAS,SAAS;GAAa,UAAU;GAAqB;GAAuB;EACvF;CACD,KAAK,MAAM,CAAC,UAAU,QAAQ,UAAU,UACtC,IAAI,aAAa,QACf,YAAY,KACV,MAAM,sBAAsB,OAAO,sCAAsC,QAAQ,CAClF;CAIL,IAAI,cAAc,SAAS,cAAc,KACvC,YAAY,KACV,MAAM,sBAAsB,cAAc,0CAA0C,CACrF;CAEH,IAAI,SAAS,aAAa,YAAY,YACpC,YAAY,KACV,MACE,sBACA,wBACA,uDACD,CACF;CAEH,IAAI,SAAS,aAAa,UAAU,GAClC,YAAY,KACV,MACE,sBACA,wBACA,0DACD,CACF;CAGH,YAAY,MAAM,MAAM,UACtB,KAAK,SAAS,MAAM,OAChB,KAAK,OAAO,MAAM,OAChB,KACA,IACF,KAAK,SAAS,MAAM,OAClB,KAAK,OAAO,MAAM,OAChB,KACA,IACF,KAAK,UAAU,MAAM,UACnB,KACA,KAAK,UAAU,MAAM,UACnB,IACA,EACX;CACD,OAAO;;;;;;;;;;;AAYT,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAuBD,MAAM,2BAAmE,IAAI,IAAI;CAC/E,CAAC,eAAe,iCAAiC;CACjD,CAAC,wBAAwB,0CAA0C;CACnE,CAAC,uBAAuB,0CAA0C;CAClE,CAAC,eAAe,iCAAiC;CACjD,CAAC,WAAW,6BAA6B;CACzC,CAAC,kBAAkB,qCAAqC;CACzD,CAAC;;;;;;;;;;;;;;;AAgBF,SAAgB,kBAAkB,YAAoD;CACpF,QAAQ,WAAW,MAAnB;EACE,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBAAsB;GACzB,MAAM,WAAW,yBAAyB,IAAI,WAAW,KAAK;GAC9D,IAAI,aAAa,KAAA,GACf,OAAO;GAET;;EAEF,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,KAAK,sBACH,OAAO;EACT,SACE;;CAEJ,IAAI,WAAW,KAAK,WAAW,SAAS,EACtC,OAAO;CAET,OAAO,WAAW,KAAK,WAAW,cAAc,GAAG,uBAAuB;;;;;;;;;;;;;AAc5E,eAAsB,uBACpB,UACA,WACA,aACA,KACmC;CACnC,MAAM,cAAc,MAAM,aAAa,UAAU,WAAW,aAAa,IAAI;CAC7E,MAAM,cAAc,CAAC,GAAG,IAAI,IAAI,YAAY,IAAI,kBAAkB,CAAC,CAAC,CAAC,MAAM;CAC3E,OAAO;EAAE,SAAS,YAAY,WAAW;EAAG;EAAa;EAAa"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://vizejs.dev/schemas/marquette/test-run-admission.schema.json",
|
|
4
|
+
"title": "Vize Test Run Admission Decision",
|
|
5
|
+
"description": "Language-neutral allow-or-deny decision that one test-run evidence record produces for one exact release candidate at one instant. Every backend family (JavaScript, Rust, Go, JVM) must produce byte-equivalent decisions for the same evidence, candidate, admission id, and admission time; the shared tests/fixtures/test-run-evidence admission-decision fixtures are the conformance source of truth for new host implementations.",
|
|
6
|
+
"$ref": "#/$defs/decision",
|
|
7
|
+
"$defs": {
|
|
8
|
+
"denialCode": {
|
|
9
|
+
"description": "Stable machine-readable cause class of one admission denial. The vocabulary is append-only: codes are never renamed, renumbered, reused, or removed, and every new distinguishable rejection cause ships with a new code added to this enum in lexicographic position. Hosts must map every diagnostic to exactly one code: VIZE_MARQUETTE_141 to admission-id-malformed; VIZE_MARQUETTE_142 to admission-id-mismatch; VIZE_MARQUETTE_144 to the candidate-*-mismatch code named by its diagnostic path (application, artifact.fingerprint, contractFingerprint, environment, release, sourceRevision); VIZE_MARQUETTE_145 to record-expired; VIZE_MARQUETTE_146 to verification-not-accepted; VIZE_MARQUETTE_147 to skipped-tests-recorded; VIZE_MARQUETTE_148 to admission-time-malformed; VIZE_MARQUETTE_149 to check-candidate-mismatch; VIZE_MARQUETTE_150 to check-invalid; VIZE_MARQUETTE_151 to check-observer-not-independent; VIZE_MARQUETTE_156 to transition-state-mismatch; VIZE_MARQUETTE_157 to transition-chain-broken; VIZE_MARQUETTE_158 to transition-replayed; VIZE_MARQUETTE_159 to transition-state-mismatch; every other diagnostic whose path starts with check. to check-invalid; every other diagnostic whose path starts with transition. to transition-invalid; and every remaining diagnostic, including every record-validation code, to record-invalid.",
|
|
10
|
+
"enum": [
|
|
11
|
+
"admission-id-malformed",
|
|
12
|
+
"admission-id-mismatch",
|
|
13
|
+
"admission-time-malformed",
|
|
14
|
+
"candidate-application-mismatch",
|
|
15
|
+
"candidate-artifact-fingerprint-mismatch",
|
|
16
|
+
"candidate-contract-fingerprint-mismatch",
|
|
17
|
+
"candidate-environment-mismatch",
|
|
18
|
+
"candidate-release-mismatch",
|
|
19
|
+
"candidate-source-revision-mismatch",
|
|
20
|
+
"check-candidate-mismatch",
|
|
21
|
+
"check-invalid",
|
|
22
|
+
"check-observer-not-independent",
|
|
23
|
+
"record-expired",
|
|
24
|
+
"record-invalid",
|
|
25
|
+
"skipped-tests-recorded",
|
|
26
|
+
"transition-chain-broken",
|
|
27
|
+
"transition-invalid",
|
|
28
|
+
"transition-replayed",
|
|
29
|
+
"transition-state-mismatch",
|
|
30
|
+
"verification-not-accepted"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"diagnostic": {
|
|
34
|
+
"description": "One stable, source-addressable diagnostic explaining a denial cause. Codes, severities, paths, messages, and ordering are identical across host families.",
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"required": ["code", "severity", "path", "message"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"code": {
|
|
40
|
+
"description": "Stable machine-readable diagnostic code.",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"pattern": "^VIZE_MARQUETTE_[0-9]{3}$"
|
|
43
|
+
},
|
|
44
|
+
"severity": {
|
|
45
|
+
"description": "Diagnostic severity; any diagnostic denies admission.",
|
|
46
|
+
"enum": ["error", "warning"]
|
|
47
|
+
},
|
|
48
|
+
"path": {
|
|
49
|
+
"description": "JSON-style path into the record or the admission input.",
|
|
50
|
+
"type": "string",
|
|
51
|
+
"minLength": 1
|
|
52
|
+
},
|
|
53
|
+
"message": {
|
|
54
|
+
"description": "Human-readable explanation and next action.",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 1
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"decision": {
|
|
61
|
+
"description": "Structured admission decision. allowed is true only when denialCodes and diagnostics are both empty. denialCodes maps every diagnostic to its denial code, deduplicated and sorted lexicographically by Unicode code point. diagnostics keeps the stable path, code, message order shared by every implementation.",
|
|
62
|
+
"type": "object",
|
|
63
|
+
"additionalProperties": false,
|
|
64
|
+
"required": ["allowed", "denialCodes", "diagnostics"],
|
|
65
|
+
"properties": {
|
|
66
|
+
"allowed": {
|
|
67
|
+
"description": "Whether the record admits the candidate.",
|
|
68
|
+
"type": "boolean"
|
|
69
|
+
},
|
|
70
|
+
"denialCodes": {
|
|
71
|
+
"description": "Deduplicated denial causes in lexicographic order; empty when allowed.",
|
|
72
|
+
"type": "array",
|
|
73
|
+
"uniqueItems": true,
|
|
74
|
+
"items": { "$ref": "#/$defs/denialCode" }
|
|
75
|
+
},
|
|
76
|
+
"diagnostics": {
|
|
77
|
+
"description": "Complete denial diagnostics; empty when allowed.",
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": { "$ref": "#/$defs/diagnostic" }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { i as TestRunEvidence } from "./test-run-model-DkllmEsY.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/test-run-canonical.d.ts
|
|
4
|
+
/** Prefix of every test-run deployment admission id. */
|
|
5
|
+
declare const TEST_RUN_ADMISSION_PREFIX = "test-run:";
|
|
6
|
+
/**
|
|
7
|
+
* Serializes a test-run evidence record canonically.
|
|
8
|
+
*
|
|
9
|
+
* Property order matches the record schema, targets sort by id, suites sort
|
|
10
|
+
* by id then shard index, and selection identifiers sort lexicographically,
|
|
11
|
+
* so equivalent records produce byte-identical JSON in every language. Call
|
|
12
|
+
* validation before trusting the record; canonicalization does not make an
|
|
13
|
+
* invalid record valid.
|
|
14
|
+
*/
|
|
15
|
+
declare function canonicalTestRunJson(evidence: TestRunEvidence): string;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the lowercase SHA-256 fingerprint of the canonical record.
|
|
18
|
+
*
|
|
19
|
+
* The fingerprint is the exact value admitted as `test-run:<sha256>` by
|
|
20
|
+
* deployment gates. Uses the Web Crypto API available in every supported
|
|
21
|
+
* runtime.
|
|
22
|
+
*/
|
|
23
|
+
declare function testRunFingerprint(evidence: TestRunEvidence): Promise<string>;
|
|
24
|
+
/** Returns the `test-run:<sha256>` admission id for one record. */
|
|
25
|
+
declare function testRunAdmissionId(evidence: TestRunEvidence): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the fingerprint named by a `test-run:<sha256>` admission id.
|
|
28
|
+
*
|
|
29
|
+
* Returns `undefined` unless the prefix, length, and lowercase hexadecimal
|
|
30
|
+
* grammar are all exact.
|
|
31
|
+
*/
|
|
32
|
+
declare function parseTestRunAdmissionId(id: string): string | undefined;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { testRunFingerprint as a, testRunAdmissionId as i, canonicalTestRunJson as n, parseTestRunAdmissionId as r, TEST_RUN_ADMISSION_PREFIX as t };
|
|
35
|
+
//# sourceMappingURL=test-run-canonical-CB-KzZxk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-run-canonical-CB-KzZxk.d.mts","names":[],"sources":["../src/test-run-canonical.ts"],"mappings":";;;;cAQa,yBAAA;AAAb;;;;;AAwCA;;;;AAxCA,iBAwCgB,oBAAA,CAAqB,QAAA,EAAU,eAAA;AA6D/C;;;;;;;AAAA,iBAAsB,kBAAA,CAAmB,QAAA,EAAU,eAAA,GAAkB,OAAA;;iBAW/C,kBAAA,CAAmB,QAAA,EAAU,eAAA,GAAkB,OAAA;;;;;;;iBAUrD,uBAAA,CAAwB,EAAA"}
|
|
@@ -1,35 +1,2 @@
|
|
|
1
|
-
import { i as
|
|
2
|
-
|
|
3
|
-
//#region src/test-run-canonical.d.ts
|
|
4
|
-
/** Prefix of every test-run deployment admission id. */
|
|
5
|
-
declare const TEST_RUN_ADMISSION_PREFIX = "test-run:";
|
|
6
|
-
/**
|
|
7
|
-
* Serializes a test-run evidence record canonically.
|
|
8
|
-
*
|
|
9
|
-
* Property order matches the record schema, targets sort by id, suites sort
|
|
10
|
-
* by id then shard index, and selection identifiers sort lexicographically,
|
|
11
|
-
* so equivalent records produce byte-identical JSON in every language. Call
|
|
12
|
-
* validation before trusting the record; canonicalization does not make an
|
|
13
|
-
* invalid record valid.
|
|
14
|
-
*/
|
|
15
|
-
declare function canonicalTestRunJson(evidence: TestRunEvidence): string;
|
|
16
|
-
/**
|
|
17
|
-
* Returns the lowercase SHA-256 fingerprint of the canonical record.
|
|
18
|
-
*
|
|
19
|
-
* The fingerprint is the exact value admitted as `test-run:<sha256>` by
|
|
20
|
-
* deployment gates. Uses the Web Crypto API available in every supported
|
|
21
|
-
* runtime.
|
|
22
|
-
*/
|
|
23
|
-
declare function testRunFingerprint(evidence: TestRunEvidence): Promise<string>;
|
|
24
|
-
/** Returns the `test-run:<sha256>` admission id for one record. */
|
|
25
|
-
declare function testRunAdmissionId(evidence: TestRunEvidence): Promise<string>;
|
|
26
|
-
/**
|
|
27
|
-
* Returns the fingerprint named by a `test-run:<sha256>` admission id.
|
|
28
|
-
*
|
|
29
|
-
* Returns `undefined` unless the prefix, length, and lowercase hexadecimal
|
|
30
|
-
* grammar are all exact.
|
|
31
|
-
*/
|
|
32
|
-
declare function parseTestRunAdmissionId(id: string): string | undefined;
|
|
33
|
-
//#endregion
|
|
34
|
-
export { TEST_RUN_ADMISSION_PREFIX, canonicalTestRunJson, parseTestRunAdmissionId, testRunAdmissionId, testRunFingerprint };
|
|
35
|
-
//# sourceMappingURL=test-run-canonical.d.mts.map
|
|
1
|
+
import { a as testRunFingerprint, i as testRunAdmissionId, n as canonicalTestRunJson, r as parseTestRunAdmissionId, t as TEST_RUN_ADMISSION_PREFIX } from "./test-run-canonical-CB-KzZxk.mjs";
|
|
2
|
+
export { TEST_RUN_ADMISSION_PREFIX, canonicalTestRunJson, parseTestRunAdmissionId, testRunAdmissionId, testRunFingerprint };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { i as TestRunEvidence } from "./test-run-model-DkllmEsY.mjs";
|
|
2
|
+
import { MarquetteDiagnostic } from "./validate.mjs";
|
|
3
|
+
import { TestRunAdmissionDecision, TestRunCandidate } from "./test-run-admission.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/test-run-check.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Serialized `format` marker for retained tests-check records.
|
|
8
|
+
*
|
|
9
|
+
* Readers must reject any other value before trusting the record.
|
|
10
|
+
*/
|
|
11
|
+
declare const TEST_RUN_CHECK_FORMAT = "vize.test-run.check";
|
|
12
|
+
/**
|
|
13
|
+
* Current serialized tests-check format.
|
|
14
|
+
*
|
|
15
|
+
* Readers must reject a higher value until they explicitly support it.
|
|
16
|
+
*/
|
|
17
|
+
declare const TEST_RUN_CHECK_FORMAT_VERSION = 1;
|
|
18
|
+
/**
|
|
19
|
+
* Retained, release-bound `tests` check for one deployment decision.
|
|
20
|
+
*
|
|
21
|
+
* The record replaces every generic test-result reference — a summary blob,
|
|
22
|
+
* a report path, or a green workflow label — with the exact
|
|
23
|
+
* `test-run:<sha256>` admission id of an independently verified run, the six
|
|
24
|
+
* candidate facts the run was admitted for, and the identity and instant of
|
|
25
|
+
* the independent observer that recorded the admission. A release decision
|
|
26
|
+
* retaining anything else as its tests evidence cannot pass
|
|
27
|
+
* {@link verifyTestRunCheck}.
|
|
28
|
+
*/
|
|
29
|
+
interface TestRunCheck {
|
|
30
|
+
/** Serialized format marker; always {@link TEST_RUN_CHECK_FORMAT}. */
|
|
31
|
+
readonly format: typeof TEST_RUN_CHECK_FORMAT;
|
|
32
|
+
/**
|
|
33
|
+
* Serialized format version.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to {@link TEST_RUN_CHECK_FORMAT_VERSION}.
|
|
36
|
+
*/
|
|
37
|
+
readonly formatVersion?: typeof TEST_RUN_CHECK_FORMAT_VERSION;
|
|
38
|
+
/** Exact `test-run:<sha256>` admission id of the observed run. */
|
|
39
|
+
readonly evidence: string;
|
|
40
|
+
/** Exact candidate facts the run was admitted for. */
|
|
41
|
+
readonly candidate: TestRunCandidate;
|
|
42
|
+
/**
|
|
43
|
+
* Identity of the independent observer that recorded the admission.
|
|
44
|
+
*
|
|
45
|
+
* The observer is the trusted promotion boundary, never the runner that
|
|
46
|
+
* executed the tests.
|
|
47
|
+
*/
|
|
48
|
+
readonly observer: string;
|
|
49
|
+
/** Millisecond-precision UTC instant the admission was observed. */
|
|
50
|
+
readonly observedAt: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validates a retained tests-check record structurally.
|
|
54
|
+
*
|
|
55
|
+
* Diagnostics use `check.` paths and are deterministic and sorted by path,
|
|
56
|
+
* code, and message. A generic evidence reference fails here with
|
|
57
|
+
* `VIZE_MARQUETTE_141`: only an exact `test-run:<sha256>` admission id can
|
|
58
|
+
* name retained test evidence. Structural validity never admits anything by
|
|
59
|
+
* itself; {@link verifyTestRunCheck} must confirm the record against the
|
|
60
|
+
* caller's candidate and the retained run. Codes, paths, messages, and
|
|
61
|
+
* ordering are identical to the native implementation.
|
|
62
|
+
*/
|
|
63
|
+
declare function validateTestRunCheck(check: TestRunCheck): MarquetteDiagnostic[];
|
|
64
|
+
/**
|
|
65
|
+
* Verifies one retained tests check against the caller's own facts.
|
|
66
|
+
*
|
|
67
|
+
* The caller supplies the candidate it is deciding from its own trusted
|
|
68
|
+
* facts; the retained check must validate structurally, bind that candidate
|
|
69
|
+
* exactly, name an observer independent from the run's runner, and be
|
|
70
|
+
* observed no earlier than the run's completed verification. The referenced
|
|
71
|
+
* record is then admitted exactly like {@link admitTestRun}: canonical
|
|
72
|
+
* fingerprint, candidate bindings, expiry at `now`, verification outcome,
|
|
73
|
+
* and skipped-test accounting all fail closed. Diagnostics, denial codes,
|
|
74
|
+
* and ordering are identical to the native implementation, as pinned by the
|
|
75
|
+
* shared check-decision fixtures.
|
|
76
|
+
*/
|
|
77
|
+
declare function verifyTestRunCheck(check: TestRunCheck, candidate: TestRunCandidate, evidence: TestRunEvidence, now: string): Promise<TestRunAdmissionDecision>;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { TEST_RUN_CHECK_FORMAT, TEST_RUN_CHECK_FORMAT_VERSION, TestRunCheck, validateTestRunCheck, verifyTestRunCheck };
|
|
80
|
+
//# sourceMappingURL=test-run-check.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-run-check.d.mts","names":[],"sources":["../src/test-run-check.ts"],"mappings":";;;;;;;AAwBA;;;cAAa,qBAAA;;AAOb;;;;cAAa,6BAAA;AAab;;;;;;;;;;;AAAA,UAAiB,YAAA;EAUN;EAAA,SARA,MAAA,SAAe,qBAAA;EAUJ;;;;;EAAA,SAJX,aAAA,UAAuB,6BAAA;EA2BE;EAAA,SAzBzB,QAAA;EAyBmE;EAAA,SAvBnE,SAAA,EAAW,gBAAA;EAuBe;;;;AA4DrC;;EA5DqC,SAhB1B,QAAA;EA6EF;EAAA,SA3EE,UAAA;AAAA;;;;;;;;;;;;iBAcK,oBAAA,CAAqB,KAAA,EAAO,YAAA,GAAe,mBAAA;;;;;;;;;;;;;;iBA4DrC,kBAAA,CACpB,KAAA,EAAO,YAAA,EACP,SAAA,EAAW,gBAAA,EACX,QAAA,EAAU,eAAA,EACV,GAAA,WACC,OAAA,CAAQ,wBAAA"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { a as checkDigest, c as checkSourceRevision, d as isStrictTimestamp, l as checkTimestamp, o as checkIdentifier, u as error } from "./test-run-validate-C_KR031E.mjs";
|
|
2
|
+
import { parseTestRunAdmissionId } from "./test-run-canonical.mjs";
|
|
3
|
+
import { admitTestRun, testRunDenialCode } from "./test-run-admission.mjs";
|
|
4
|
+
//#region src/test-run-check.ts
|
|
5
|
+
/**
|
|
6
|
+
* Serialized `format` marker for retained tests-check records.
|
|
7
|
+
*
|
|
8
|
+
* Readers must reject any other value before trusting the record.
|
|
9
|
+
*/
|
|
10
|
+
const TEST_RUN_CHECK_FORMAT = "vize.test-run.check";
|
|
11
|
+
/**
|
|
12
|
+
* Current serialized tests-check format.
|
|
13
|
+
*
|
|
14
|
+
* Readers must reject a higher value until they explicitly support it.
|
|
15
|
+
*/
|
|
16
|
+
const TEST_RUN_CHECK_FORMAT_VERSION = 1;
|
|
17
|
+
/**
|
|
18
|
+
* Validates a retained tests-check record structurally.
|
|
19
|
+
*
|
|
20
|
+
* Diagnostics use `check.` paths and are deterministic and sorted by path,
|
|
21
|
+
* code, and message. A generic evidence reference fails here with
|
|
22
|
+
* `VIZE_MARQUETTE_141`: only an exact `test-run:<sha256>` admission id can
|
|
23
|
+
* name retained test evidence. Structural validity never admits anything by
|
|
24
|
+
* itself; {@link verifyTestRunCheck} must confirm the record against the
|
|
25
|
+
* caller's candidate and the retained run. Codes, paths, messages, and
|
|
26
|
+
* ordering are identical to the native implementation.
|
|
27
|
+
*/
|
|
28
|
+
function validateTestRunCheck(check) {
|
|
29
|
+
const diagnostics = [];
|
|
30
|
+
if (check.format !== "vize.test-run.check") diagnostics.push(error("VIZE_MARQUETTE_101", "check.format", "unsupported tests-check format marker"));
|
|
31
|
+
if ((check.formatVersion ?? 1) !== 1) diagnostics.push(error("VIZE_MARQUETTE_102", "check.formatVersion", "unsupported tests-check format version"));
|
|
32
|
+
if (parseTestRunAdmissionId(check.evidence) === void 0) diagnostics.push(error("VIZE_MARQUETTE_141", "check.evidence", "check evidence must be test-run: followed by 64 lowercase hexadecimal characters"));
|
|
33
|
+
const candidate = check.candidate;
|
|
34
|
+
checkIdentifier(candidate.application, "check.candidate.application", diagnostics);
|
|
35
|
+
checkIdentifier(candidate.environment, "check.candidate.environment", diagnostics);
|
|
36
|
+
checkDigest(candidate.contractFingerprint, "check.candidate.contractFingerprint", diagnostics);
|
|
37
|
+
checkSourceRevision(candidate.sourceRevision, "check.candidate.sourceRevision", diagnostics);
|
|
38
|
+
if (candidate.release.length === 0 || candidate.release.length > 256) diagnostics.push(error("VIZE_MARQUETTE_106", "check.candidate.release", "release must be between 1 and 256 characters"));
|
|
39
|
+
checkDigest(candidate.artifactFingerprint, "check.candidate.artifactFingerprint", diagnostics);
|
|
40
|
+
checkIdentifier(check.observer, "check.observer", diagnostics);
|
|
41
|
+
checkTimestamp(check.observedAt, "check.observedAt", diagnostics);
|
|
42
|
+
sortDiagnostics(diagnostics);
|
|
43
|
+
return diagnostics;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Verifies one retained tests check against the caller's own facts.
|
|
47
|
+
*
|
|
48
|
+
* The caller supplies the candidate it is deciding from its own trusted
|
|
49
|
+
* facts; the retained check must validate structurally, bind that candidate
|
|
50
|
+
* exactly, name an observer independent from the run's runner, and be
|
|
51
|
+
* observed no earlier than the run's completed verification. The referenced
|
|
52
|
+
* record is then admitted exactly like {@link admitTestRun}: canonical
|
|
53
|
+
* fingerprint, candidate bindings, expiry at `now`, verification outcome,
|
|
54
|
+
* and skipped-test accounting all fail closed. Diagnostics, denial codes,
|
|
55
|
+
* and ordering are identical to the native implementation, as pinned by the
|
|
56
|
+
* shared check-decision fixtures.
|
|
57
|
+
*/
|
|
58
|
+
async function verifyTestRunCheck(check, candidate, evidence, now) {
|
|
59
|
+
const diagnostics = validateTestRunCheck(check);
|
|
60
|
+
const bindings = [
|
|
61
|
+
[
|
|
62
|
+
check.candidate.application,
|
|
63
|
+
candidate.application,
|
|
64
|
+
"application",
|
|
65
|
+
"application"
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
check.candidate.environment,
|
|
69
|
+
candidate.environment,
|
|
70
|
+
"environment",
|
|
71
|
+
"environment"
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
check.candidate.contractFingerprint,
|
|
75
|
+
candidate.contractFingerprint,
|
|
76
|
+
"contractFingerprint",
|
|
77
|
+
"contract fingerprint"
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
check.candidate.sourceRevision,
|
|
81
|
+
candidate.sourceRevision,
|
|
82
|
+
"sourceRevision",
|
|
83
|
+
"source revision"
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
check.candidate.release,
|
|
87
|
+
candidate.release,
|
|
88
|
+
"release",
|
|
89
|
+
"release"
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
check.candidate.artifactFingerprint,
|
|
93
|
+
candidate.artifactFingerprint,
|
|
94
|
+
"artifactFingerprint",
|
|
95
|
+
"artifact fingerprint"
|
|
96
|
+
]
|
|
97
|
+
];
|
|
98
|
+
for (const [recorded, expected, property, field] of bindings) if (recorded !== expected) diagnostics.push(error("VIZE_MARQUETTE_149", `check.candidate.${property}`, `check does not bind the candidate ${field}`));
|
|
99
|
+
if (check.observer === evidence.runner.identity) diagnostics.push(error("VIZE_MARQUETTE_151", "check.observer", "check observer must be independent from the run's runner"));
|
|
100
|
+
if (isStrictTimestamp(check.observedAt) && check.observedAt < evidence.verification.completedAt) diagnostics.push(error("VIZE_MARQUETTE_150", "check.observedAt", "observation must not precede the completed verification"));
|
|
101
|
+
diagnostics.push(...await admitTestRun(evidence, candidate, check.evidence, now));
|
|
102
|
+
sortDiagnostics(diagnostics);
|
|
103
|
+
const denialCodes = [...new Set(diagnostics.map(testRunDenialCode))].sort();
|
|
104
|
+
return {
|
|
105
|
+
allowed: diagnostics.length === 0,
|
|
106
|
+
denialCodes,
|
|
107
|
+
diagnostics
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function sortDiagnostics(diagnostics) {
|
|
111
|
+
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);
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { TEST_RUN_CHECK_FORMAT, TEST_RUN_CHECK_FORMAT_VERSION, validateTestRunCheck, verifyTestRunCheck };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=test-run-check.mjs.map
|