Inspect constructor values and contexts without unrelated derivation closures (#931)

This commit is contained in:
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065
2026-11-05 21:32:42 +00:00
parent 61881f9cda
commit 4f672d027d
3 changed files with 59 additions and 33 deletions
+34
View File
@@ -0,0 +1,34 @@
# Read actual constructor attributes as comparison data. Their values AND
# dependency contexts remain visible; none is substituted for a build input.
derivation:
let
fields = [
"name"
"system"
"src"
"stdenv"
"nativeBuildInputs"
"buildInputs"
"propagatedBuildInputs"
"propagatedNativeBuildInputs"
"configurePhase"
"buildPhase"
"checkPhase"
"installPhase"
"doCheck"
"strictDeps"
"outputs"
"__structuredAttrs"
];
selected = builtins.intersectAttrs (builtins.listToAttrs (
map (name: {
inherit name;
value = null;
}) fields
)) derivation;
in
builtins.mapAttrs (_: value: {
type = builtins.typeOf value;
value = builtins.unsafeDiscardStringContext (toString value);
context = builtins.getContext (toString value);
}) selected
+18 -22
View File
@@ -34,6 +34,7 @@ function identity() {
derivation = compiler.drvPath; derivation = compiler.drvPath;
yarn = compiler.yarn-freestanding.drvPath; yarn = compiler.yarn-freestanding.drvPath;
source = toString compiler.src; source = toString compiler.src;
metadata = (import ((/. + ${nixString(source)}) + "/source-qualification-metadata.nix")) compiler;
}`; }`;
const result = spawnSync( const result = spawnSync(
"nix", "nix",
@@ -52,17 +53,6 @@ function identity() {
assert.equal(result.status, 0, result.error?.message ?? result.stderr); assert.equal(result.status, 0, result.error?.message ?? result.stderr);
return JSON.parse(result.stdout); return JSON.parse(result.stdout);
} }
function derivationData(file) {
const result = spawnSync(
"nix",
["--store", "local?root=" + process.env.QUIXOS_PROTOCOL_EVALUATION_STORE, "derivation", "show", file],
{ encoding: "utf8", timeout: 30000, maxBuffer: 2097152 },
);
assert.equal(result.status, 0, result.error?.message ?? result.stderr);
const entries = Object.values(JSON.parse(result.stdout));
assert.equal(entries.length, 1);
return entries[0];
}
function summarized(value) { function summarized(value) {
const text = JSON.stringify(value) ?? "undefined"; const text = JSON.stringify(value) ?? "undefined";
return text.length < 500 return text.length < 500
@@ -74,7 +64,7 @@ function summarized(value) {
}; };
} }
function structuralDifferences(actual, expected, field = "", result = []) { function structuralDifferences(actual, expected, field = "", result = []) {
if (field === "outputs" || field === "env.out" || JSON.stringify(actual) === JSON.stringify(expected)) return result; if (JSON.stringify(actual) === JSON.stringify(expected)) return result;
if ( if (
Array.isArray(actual) && Array.isArray(actual) &&
Array.isArray(expected) && Array.isArray(expected) &&
@@ -99,15 +89,24 @@ function structuralDifferences(actual, expected, field = "", result = []) {
for (const key of new Set([...Object.keys(actual), ...Object.keys(expected)])) for (const key of new Set([...Object.keys(actual), ...Object.keys(expected)]))
structuralDifferences(actual[key], expected[key], field ? field + "." + key : key, result); structuralDifferences(actual[key], expected[key], field ? field + "." + key : key, result);
} else { } else {
result.push({ field, actual: summarized(actual), expected: summarized(expected) }); const difference = { field, actual: summarized(actual), expected: summarized(expected) };
if (typeof actual === "string" && typeof expected === "string" && Math.max(actual.length, expected.length) >= 500) {
let offset = 0;
while (offset < actual.length && offset < expected.length && actual[offset] === expected[offset]) offset++;
difference.offset = offset;
difference.actualExcerpt = actual.slice(Math.max(0, offset - 80), offset + 200);
difference.expectedExcerpt = expected.slice(Math.max(0, offset - 80), offset + 200);
}
result.push(difference);
} }
return result; return result;
} }
function baselineDifference(baseline) { function baselineDifference(baseline) {
const { metadata, ...identities } = baseline;
console.log( console.log(
"PROTOCOL_SOURCE_BASELINE: " + "PROTOCOL_SOURCE_BASELINE: " +
JSON.stringify({ JSON.stringify({
actual: baseline, actual: identities,
expected: config.expected, expected: config.expected,
expectedSource: config.expectedSource, expectedSource: config.expectedSource,
evaluatorVersion: config.evaluatorVersion, evaluatorVersion: config.evaluatorVersion,
@@ -115,15 +114,10 @@ function baselineDifference(baseline) {
narHashContext: config.narHashContext, narHashContext: config.narHashContext,
}), }),
); );
try {
const actual = derivationData(baseline.derivation);
const expected = derivationData(config.productionDerivation);
console.log( console.log(
"PROTOCOL_SOURCE_BASELINE_DIFF: " + JSON.stringify(structuralDifferences(actual, expected)).slice(0, 10000), "PROTOCOL_SOURCE_BASELINE_DIFF: " +
JSON.stringify(structuralDifferences(metadata, config.expectedMetadata)).slice(0, 10000),
); );
} catch (error) {
console.log("PROTOCOL_SOURCE_BASELINE_DIAGNOSTIC_UNAVAILABLE: " + String(error.message).slice(0, 2000));
}
} }
async function edit(file, mutate, check) { async function edit(file, mutate, check) {
const before = await fs.readFile(file, "utf8"); const before = await fs.readFile(file, "utf8");
@@ -244,7 +238,9 @@ try {
JSON.stringify( JSON.stringify(
{ {
codec: "quixos-protocol-source-qualification/1", codec: "quixos-protocol-source-qualification/1",
...baseline, derivation: baseline.derivation,
yarn: baseline.yarn,
source: baseline.source,
cases, cases,
metadataContext: config.narHashContext, metadataContext: config.narHashContext,
evaluatorVersion: config.evaluatorVersion, evaluatorVersion: config.evaluatorVersion,
+5 -9
View File
@@ -5,8 +5,7 @@
}: }:
let let
comparisonIdentity = import ../tools/local-first/derivation-identity.nix; comparisonIdentity = import ../tools/local-first/derivation-identity.nix;
# Inspect the actual .drv as data, without its derivation-deep output context. inspect = import ./source-qualification-metadata.nix;
productionDerivation = builtins.unsafeDiscardOutputDependency library.drvPath;
source = pkgs.lib.fileset.toSource { source = pkgs.lib.fileset.toSource {
root = ./.; root = ./.;
fileset = pkgs.lib.fileset.unions [ fileset = pkgs.lib.fileset.unions [
@@ -23,6 +22,7 @@ let
./tsconfig.build.json ./tsconfig.build.json
./tsconfig.test.json ./tsconfig.test.json
./default.nix ./default.nix
./source-qualification-metadata.nix
./yarn-project.nix ./yarn-project.nix
./flake.nix ./flake.nix
./README.md ./README.md
@@ -30,23 +30,19 @@ let
}; };
inputs = pkgs.writeText "protocol-source-inputs.json" ( inputs = pkgs.writeText "protocol-source-inputs.json" (
builtins.toJSON { builtins.toJSON {
inherit source nixpkgsNarHash productionDerivation; inherit source nixpkgsNarHash;
narHashContext = builtins.getContext nixpkgsNarHash; narHashContext = builtins.getContext nixpkgsNarHash;
evaluatorVersion = builtins.nixVersion; evaluatorVersion = builtins.nixVersion;
pinnedNixVersion = pkgs.nix.version; pinnedNixVersion = pkgs.nix.version;
nixpkgs = pkgs.path; nixpkgs = pkgs.path;
system = pkgs.stdenv.hostPlatform.system; system = pkgs.stdenv.hostPlatform.system;
expected = comparisonIdentity library; expected = comparisonIdentity library;
expectedMetadata = inspect library;
expectedSource = builtins.unsafeDiscardStringContext (toString library.src); expectedSource = builtins.unsafeDiscardStringContext (toString library.src);
yarn = comparisonIdentity library.yarn-freestanding; yarn = comparisonIdentity library.yarn-freestanding;
} }
); );
closure = pkgs.closureInfo { closure = pkgs.closureInfo { rootPaths = [ pkgs.path ]; };
rootPaths = [
pkgs.path
productionDerivation
];
};
in in
pkgs.runCommand "quixos-protocol-source-check" pkgs.runCommand "quixos-protocol-source-check"
{ {