@dailephd/my-dev-kit-lab 0.4.3 → 0.4.5
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/CHANGELOG.md +468 -0
- package/README.md +8 -1
- package/dist/scripts/report-context-integrity-smoke.js +70 -0
- package/dist/src/evaluation/ecosystemFixtures/index.js +3 -0
- package/dist/src/evaluation/ecosystemFixtures/loadContextIntegrityFixture.js +102 -0
- package/dist/src/evaluation/ecosystemFixtures/manifestTypes.js +1 -0
- package/dist/src/evaluation/ecosystemFixtures/validateManifest.js +82 -0
- package/dist/src/evaluation/ecosystemFixtures/verifyFixtureHashes.js +45 -0
- package/dist/src/evaluation/stageContextExpectations/producerReadinessExpectationTypes.js +8 -0
- package/dist/src/evaluation/stageContextExpectations/validation.js +200 -0
- package/dist/src/evaluation/stageContextMetrics/calculateAllocationMetrics.js +265 -0
- package/dist/src/evaluation/stageContextMetrics/calculateConditionCoverageMetrics.js +74 -0
- package/dist/src/evaluation/stageContextMetrics/calculateCriticalityMetrics.js +112 -0
- package/dist/src/evaluation/stageContextMetrics/calculateOwnerMetrics.js +84 -0
- package/dist/src/evaluation/stageContextMetrics/calculateProducerConditionAgreement.js +113 -0
- package/dist/src/evaluation/stageContextMetrics/calculateReadinessAgreement.js +212 -0
- package/dist/src/evaluation/stageContextMetrics/calculateRunIntegrityAgreement.js +309 -0
- package/dist/src/evaluation/stageContextMetrics/calculateSupplementalRawAgreement.js +60 -0
- package/dist/src/evaluation/stageContextMetrics/calculateTruncationClassification.js +144 -0
- package/dist/src/evaluation/stageContextMetrics/evaluateProducerReadinessBridge.js +198 -0
- package/dist/src/evaluation/stageContextMetrics/index.js +11 -0
- package/dist/src/evaluation/stageContextMetrics/producerReadinessMetricTypes.js +1 -0
- package/dist/src/evaluation/stageContextMetrics/runIntegrityAgreementTypes.js +1 -0
- package/dist/src/evaluation/stageContextSelectors/contextArtifactConsistency.js +17 -0
- package/dist/src/evaluation/stageContextSelectors/index.js +1 -0
- package/dist/src/evaluation/stageContextSelectors/orchestratorRunIntegritySelectors.js +64 -0
- package/dist/src/evaluation/upstreamArtifacts/index.js +8 -0
- package/dist/src/evaluation/upstreamArtifacts/orchestratorContextReadinessResultV1.js +247 -0
- package/dist/src/evaluation/upstreamArtifacts/orchestratorRunIntegrityV1.js +33 -0
- package/dist/src/evaluation/upstreamArtifacts/readImplementationContextPacketV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readImplementationContextRetrievalReportV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readOrchestratorRunIntegrityV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readSupplementalContextTextFile.js +29 -0
- package/dist/src/evaluation/upstreamArtifacts/readTestContextPacketV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readTestContextRetrievalReportV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/supplementalContextTextParser.js +75 -0
- package/dist/src/evaluation/upstreamArtifacts/supplementalContextTypes.js +152 -0
- package/dist/src/evaluation/upstreamArtifacts/validateMyDevKitContextCapsuleV1.js +59 -2
- package/dist/src/evaluation/upstreamArtifacts/validateMyDevKitRetrievalAuditRecordV1.js +15 -3
- package/dist/src/evaluation/upstreamArtifacts/validateOrchestratorRunIntegrityV1.js +177 -0
- package/dist/src/evaluation/upstreamArtifacts/validateSupplementalContextDocumentV1.js +148 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/loadV043StrategyArtifacts.js +117 -6
- package/dist/src/experiments/plugins/contextStrategyComparison/runV043StageContextStrategyWithAssurance.js +18 -3
- package/dist/src/experiments/plugins/contextStrategyComparison/validateV043StrategyInput.js +17 -3
- package/dist/src/report/experiments/buildContextIntegrityReport.js +204 -0
- package/dist/src/report/experiments/buildContextStrategyComparisonV043Report.js +122 -0
- package/dist/src/report/experiments/contextIntegrityReportModel.js +1 -0
- package/dist/src/report/experiments/index.js +5 -0
- package/dist/src/report/experiments/renderContextIntegrityHtml.js +98 -0
- package/dist/src/report/experiments/renderContextIntegrityJsonReport.js +3 -0
- package/dist/src/report/experiments/renderContextIntegrityText.js +111 -0
- package/dist/src/report/experiments/renderContextStrategyComparisonV043Html.js +62 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +67 -0
- package/dist/src/securityValidation/packageChecks/requiredPackageContents.js +33 -0
- package/dist/src/securityValidation/packageChecks/runPackageChecks.js +11 -5
- package/docs/METRICS.md +111 -0
- package/package.json +5 -3
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/derived/run-integrity-evidence.json +99 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-audit.json +1585 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-capsule.json +11455 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/00-request.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/architecture-context-packet.txt +82 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/behavior-model.txt +104 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/final-report.txt +41 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-context-packet.txt +114 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-report.txt +92 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/judge-report.txt +34 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/pseudocode-packet.txt +244 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/request-brief.txt +40 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-context-packet.txt +93 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-implementation-report.txt +69 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-strategy-packet.txt +354 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/verification-report.txt +85 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/derived/run-integrity-evidence.json +98 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-audit.json +1169 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-request.json +29 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-audit.json +1494 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-capsule.json +11364 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-request.json +33 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/test-request.json +74 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/architecture-context-retrieval-report.txt +58 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/implementation-context-retrieval-report.txt +75 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/test-context-retrieval-report.txt +84 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/run.json +62 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/my-dev-kit-version.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/orchestrator-version.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/corrected-replay-manifest.json +19 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/failed-run-manifest.json +56 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/PROVENANCE.txt +18 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/context-capsule/complete-v1.0.0.json +556 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/retrieval-audit-record/complete-v1.0.0.json +238 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/PROVENANCE.txt +13 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/context-capsule.json +7874 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/retrieval-audit-record.json +1076 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/PROVENANCE.txt +31 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/ready-pass-final-pass.json +132 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/refresh-required-need-context.json +146 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// v0.4.5 Batch 4: bounded end-to-end fixture loader (section 22). Loads a frozen
|
|
2
|
+
// context-integrity ecosystem fixture (failed-run or corrected-replay) through the existing
|
|
3
|
+
// Batch 1 producer readers and Batch 3 run-integrity reader, verifies hashes, and rejects
|
|
4
|
+
// path traversal. It never recalculates metrics itself -- evaluation happens through the
|
|
5
|
+
// existing evaluateProducerReadinessBridge, called by the test/consumer, not by this loader.
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { readMyDevKitContextCapsuleV1 } from "../upstreamArtifacts/readMyDevKitContextCapsuleV1.js";
|
|
9
|
+
import { readMyDevKitRetrievalAuditRecordV1 } from "../upstreamArtifacts/readMyDevKitRetrievalAuditRecordV1.js";
|
|
10
|
+
import { readOrchestratorRunIntegrityEvidenceV1 } from "../upstreamArtifacts/readOrchestratorRunIntegrityV1.js";
|
|
11
|
+
import { isSafeFixtureRelativePath, validateEcosystemFixtureManifest } from "./validateManifest.js";
|
|
12
|
+
import { verifyFixtureHashes } from "./verifyFixtureHashes.js";
|
|
13
|
+
function findEntry(manifest, role) {
|
|
14
|
+
return manifest.artifacts.find((a) => a.role === role && a.evaluationUse);
|
|
15
|
+
}
|
|
16
|
+
export async function loadContextIntegrityFixture(fixtureRoot, manifestPath) {
|
|
17
|
+
const issues = [];
|
|
18
|
+
let manifestRaw;
|
|
19
|
+
try {
|
|
20
|
+
manifestRaw = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
manifest: null,
|
|
26
|
+
hashVerification: null,
|
|
27
|
+
implementationCapsule: null,
|
|
28
|
+
implementationAudit: null,
|
|
29
|
+
runIntegrityEvidence: null,
|
|
30
|
+
issues: [`Manifest at "${manifestPath}" could not be read or parsed: ${error.message}`]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const manifestResult = validateEcosystemFixtureManifest(manifestRaw);
|
|
34
|
+
if (!manifestResult.ok) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
manifest: null,
|
|
38
|
+
hashVerification: null,
|
|
39
|
+
implementationCapsule: null,
|
|
40
|
+
implementationAudit: null,
|
|
41
|
+
runIntegrityEvidence: null,
|
|
42
|
+
issues: manifestResult.issues.map((i) => `[${i.code}] ${i.fieldPath}: ${i.message}`)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const manifest = manifestResult.manifest;
|
|
46
|
+
const hashVerification = await verifyFixtureHashes(manifest, fixtureRoot);
|
|
47
|
+
if (!hashVerification.ok) {
|
|
48
|
+
issues.push(...hashVerification.issues.map((i) => `[${i.code}] ${i.fixtureRelativePath}: ${i.message}`));
|
|
49
|
+
}
|
|
50
|
+
function resolveSafe(relativePath) {
|
|
51
|
+
if (!isSafeFixtureRelativePath(relativePath)) {
|
|
52
|
+
issues.push(`Refused to resolve unsafe fixture-relative path "${relativePath}".`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return path.resolve(fixtureRoot, relativePath);
|
|
56
|
+
}
|
|
57
|
+
let implementationCapsule = null;
|
|
58
|
+
const capsuleEntry = findEntry(manifest, "context-capsule");
|
|
59
|
+
if (capsuleEntry) {
|
|
60
|
+
const resolved = resolveSafe(capsuleEntry.fixtureRelativePath);
|
|
61
|
+
if (resolved) {
|
|
62
|
+
const result = await readMyDevKitContextCapsuleV1(resolved);
|
|
63
|
+
if (result.ok)
|
|
64
|
+
implementationCapsule = result.artifact;
|
|
65
|
+
else
|
|
66
|
+
issues.push(`context-capsule reader failed for "${capsuleEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let implementationAudit = null;
|
|
70
|
+
const auditEntry = findEntry(manifest, "retrieval-audit-record");
|
|
71
|
+
if (auditEntry) {
|
|
72
|
+
const resolved = resolveSafe(auditEntry.fixtureRelativePath);
|
|
73
|
+
if (resolved) {
|
|
74
|
+
const result = await readMyDevKitRetrievalAuditRecordV1(resolved);
|
|
75
|
+
if (result.ok)
|
|
76
|
+
implementationAudit = result.artifact;
|
|
77
|
+
else
|
|
78
|
+
issues.push(`retrieval-audit-record reader failed for "${auditEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let runIntegrityEvidence = null;
|
|
82
|
+
const runIntegrityEntry = findEntry(manifest, "run-integrity-evidence");
|
|
83
|
+
if (runIntegrityEntry) {
|
|
84
|
+
const resolved = resolveSafe(runIntegrityEntry.fixtureRelativePath);
|
|
85
|
+
if (resolved) {
|
|
86
|
+
const result = await readOrchestratorRunIntegrityEvidenceV1(resolved);
|
|
87
|
+
if (result.ok)
|
|
88
|
+
runIntegrityEvidence = result.artifact;
|
|
89
|
+
else
|
|
90
|
+
issues.push(`run-integrity-evidence reader failed for "${runIntegrityEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
ok: issues.length === 0,
|
|
95
|
+
manifest,
|
|
96
|
+
hashVerification,
|
|
97
|
+
implementationCapsule,
|
|
98
|
+
implementationAudit,
|
|
99
|
+
runIntegrityEvidence,
|
|
100
|
+
issues
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
// Rejects any fixture-relative path that is absolute, empty, or escapes the fixture root
|
|
5
|
+
// via ".." segments (section 22 "rejects path traversal"). Windows and POSIX separators are
|
|
6
|
+
// both checked since fixture manifests are authored on Windows but tests may run on either.
|
|
7
|
+
export function isSafeFixtureRelativePath(candidate) {
|
|
8
|
+
if (candidate.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
if (candidate.startsWith("/") || candidate.startsWith("\\"))
|
|
11
|
+
return false;
|
|
12
|
+
if (/^[a-zA-Z]:[\\/]/.test(candidate))
|
|
13
|
+
return false;
|
|
14
|
+
const segments = candidate.split(/[\\/]/);
|
|
15
|
+
return segments.every((segment) => segment !== "..") && segments.length > 0;
|
|
16
|
+
}
|
|
17
|
+
export function validateEcosystemFixtureManifest(value) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
if (!isPlainObject(value)) {
|
|
20
|
+
return { ok: false, issues: [{ code: "NON_OBJECT_MANIFEST", fieldPath: "", message: "Manifest must be a plain JSON object." }] };
|
|
21
|
+
}
|
|
22
|
+
const schemaVersion = value["manifestSchemaVersion"];
|
|
23
|
+
if (typeof schemaVersion !== "string" || !/^1\./.test(schemaVersion)) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
issues: [
|
|
27
|
+
{
|
|
28
|
+
code: "UNSUPPORTED_MANIFEST_SCHEMA_MAJOR",
|
|
29
|
+
fieldPath: "manifestSchemaVersion",
|
|
30
|
+
message: `Manifest schema version "${String(schemaVersion)}" is unsupported; expected major version 1.`
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const requiredStringFields = ["fixtureId", "description", "sourceEvidenceRoot", "generatedAt"];
|
|
36
|
+
for (const field of requiredStringFields) {
|
|
37
|
+
if (typeof value[field] !== "string" || value[field].length === 0) {
|
|
38
|
+
issues.push({ code: "MISSING_REQUIRED_FIELD", fieldPath: field, message: `Manifest is missing required non-empty string field "${field}".` });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const artifacts = value["artifacts"];
|
|
42
|
+
if (!Array.isArray(artifacts)) {
|
|
43
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath: "artifacts", message: 'Manifest field "artifacts" must be an array.' });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const seenPaths = new Set();
|
|
47
|
+
artifacts.forEach((entry, index) => {
|
|
48
|
+
const fieldPath = `artifacts[${index}]`;
|
|
49
|
+
if (!isPlainObject(entry)) {
|
|
50
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath, message: "Each artifact entry must be an object." });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const relPath = entry["fixtureRelativePath"];
|
|
54
|
+
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
55
|
+
issues.push({ code: "MISSING_REQUIRED_FIELD", fieldPath: `${fieldPath}.fixtureRelativePath`, message: "Missing fixtureRelativePath." });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!isSafeFixtureRelativePath(relPath)) {
|
|
59
|
+
issues.push({
|
|
60
|
+
code: "PATH_ESCAPES_FIXTURE_ROOT",
|
|
61
|
+
fieldPath: `${fieldPath}.fixtureRelativePath`,
|
|
62
|
+
message: `fixtureRelativePath "${relPath}" is absolute or escapes the fixture root.`
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (seenPaths.has(relPath)) {
|
|
67
|
+
issues.push({ code: "DUPLICATE_ARTIFACT_PATH", fieldPath: `${fieldPath}.fixtureRelativePath`, message: `Duplicate fixtureRelativePath "${relPath}".` });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
seenPaths.add(relPath);
|
|
71
|
+
for (const hashField of ["originalSha256", "copiedSha256"]) {
|
|
72
|
+
const v = entry[hashField];
|
|
73
|
+
if (typeof v !== "string" || !/^[0-9a-f]{64}$/i.test(v)) {
|
|
74
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath: `${fieldPath}.${hashField}`, message: `${hashField} must be a 64-hex-character SHA-256 digest.` });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (issues.length > 0)
|
|
80
|
+
return { ok: false, issues };
|
|
81
|
+
return { ok: true, manifest: value };
|
|
82
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isSafeFixtureRelativePath } from "./validateManifest.js";
|
|
5
|
+
async function sha256OfFile(absolutePath) {
|
|
6
|
+
const contents = await readFile(absolutePath);
|
|
7
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
// Section 21: reads the manifest, hashes each tracked fixture file, and reports missing
|
|
10
|
+
// files, hash mismatches, and path-traversal attempts with deterministic (manifest) order.
|
|
11
|
+
// Never rewrites the manifest or the fixture; a mismatch is reported, not repaired.
|
|
12
|
+
export async function verifyFixtureHashes(manifest, fixtureRoot) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
let checkedCount = 0;
|
|
15
|
+
for (const artifact of manifest.artifacts) {
|
|
16
|
+
const relPath = artifact.fixtureRelativePath;
|
|
17
|
+
if (!isSafeFixtureRelativePath(relPath)) {
|
|
18
|
+
issues.push({ code: "PATH_ESCAPES_FIXTURE_ROOT", fixtureRelativePath: relPath, message: `Manifest entry references an unsafe path: "${relPath}".` });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const absolutePath = path.resolve(fixtureRoot, relPath);
|
|
22
|
+
let actualSha256;
|
|
23
|
+
try {
|
|
24
|
+
actualSha256 = await sha256OfFile(absolutePath);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const code = error.code;
|
|
28
|
+
issues.push({
|
|
29
|
+
code: code === "ENOENT" ? "MISSING_FILE" : "UNREADABLE_FILE",
|
|
30
|
+
fixtureRelativePath: relPath,
|
|
31
|
+
message: code === "ENOENT" ? `Fixture file is missing: "${relPath}".` : `Fixture file could not be read: "${relPath}" (${error.message}).`
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
checkedCount += 1;
|
|
36
|
+
if (actualSha256 !== artifact.copiedSha256) {
|
|
37
|
+
issues.push({
|
|
38
|
+
code: "HASH_MISMATCH",
|
|
39
|
+
fixtureRelativePath: relPath,
|
|
40
|
+
message: `Fixture file "${relPath}" hash does not match the manifest's recorded copiedSha256 (expected ${artifact.copiedSha256}, got ${actualSha256}).`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { ok: issues.length === 0, checkedCount, issues };
|
|
45
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// v0.4.4 Batch 2: additive, optional expectation facets covering the producer/readiness
|
|
2
|
+
// evidence Batch 1 preserves (selected owners, allocation/truncation evidence on the raw
|
|
3
|
+
// my-dev-kit artifacts, orchestrator readiness, and responsibility criticality). These are
|
|
4
|
+
// a separate axis from StageContextExpectationItemV1/expectedEvidence -- they are the
|
|
5
|
+
// deterministic lab oracle for Batch 2 metrics, not evidence-match expectations. Fully
|
|
6
|
+
// optional so every existing v0.4.3 StageContextExpectationFixtureV1 remains valid and
|
|
7
|
+
// unchanged (schema major stays 1).
|
|
8
|
+
export {};
|
|
@@ -199,6 +199,11 @@ export function validateStageContextExpectationFixtureV1(value, sourcePath) {
|
|
|
199
199
|
const statesFailure = validateExpectedStates(root.expectedStates, sourcePath);
|
|
200
200
|
if (statesFailure)
|
|
201
201
|
return statesFailure;
|
|
202
|
+
if (root.producerReadinessExpectations !== undefined) {
|
|
203
|
+
const producerReadinessFailure = validateProducerReadinessExpectations(root.producerReadinessExpectations, sourcePath);
|
|
204
|
+
if (producerReadinessFailure)
|
|
205
|
+
return producerReadinessFailure;
|
|
206
|
+
}
|
|
202
207
|
return {
|
|
203
208
|
ok: true,
|
|
204
209
|
sourcePath,
|
|
@@ -566,3 +571,198 @@ function validateExpectedStates(statesValue, sourcePath) {
|
|
|
566
571
|
return immutabilityFailure;
|
|
567
572
|
return null;
|
|
568
573
|
}
|
|
574
|
+
// v0.4.4 Batch 2: additive, optional producer/readiness expectation validation. Structural
|
|
575
|
+
// classification only, mirroring the existing expectation-item validation conventions.
|
|
576
|
+
const OWNER_INCLUSION_VALUES = ["required", "allowed", "forbidden"];
|
|
577
|
+
const READINESS_KIND_VALUES = ["implementation", "test"];
|
|
578
|
+
const READINESS_DECISION_VALUES = ["not-required", "ready", "refresh-required"];
|
|
579
|
+
const CRITICALITY_VALUES = ["critical", "noncritical"];
|
|
580
|
+
const PRODUCER_READINESS_SOURCE_ARTIFACT_VALUES = ["context-capsule", "retrieval-audit-record"];
|
|
581
|
+
function requireNonEmptyExpectationId(item, itemPath, sourcePath) {
|
|
582
|
+
return requireNonEmptyString(item, "expectationId", `${itemPath}.expectationId`, sourcePath);
|
|
583
|
+
}
|
|
584
|
+
function validateOwnerExpectation(itemValue, itemPath, sourcePath) {
|
|
585
|
+
if (!isPlainObject(itemValue)) {
|
|
586
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
587
|
+
}
|
|
588
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
589
|
+
if (idFailure)
|
|
590
|
+
return idFailure;
|
|
591
|
+
if (!OWNER_INCLUSION_VALUES.includes(itemValue.inclusion)) {
|
|
592
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.inclusion is not valid.`, {
|
|
593
|
+
fieldPath: `${itemPath}.inclusion`,
|
|
594
|
+
actual: itemValue.inclusion
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (!PRODUCER_READINESS_SOURCE_ARTIFACT_VALUES.includes(itemValue.sourceArtifact)) {
|
|
598
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.sourceArtifact is not valid.`, {
|
|
599
|
+
fieldPath: `${itemPath}.sourceArtifact`,
|
|
600
|
+
actual: itemValue.sourceArtifact
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
const ownerIdFailure = requireNonEmptyString(itemValue, "ownerId", `${itemPath}.ownerId`, sourcePath);
|
|
604
|
+
if (ownerIdFailure)
|
|
605
|
+
return ownerIdFailure;
|
|
606
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
607
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
608
|
+
fieldPath: `${itemPath}.notes`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
function validateAllocationExpectation(itemValue, itemPath, sourcePath) {
|
|
614
|
+
if (!isPlainObject(itemValue)) {
|
|
615
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
616
|
+
}
|
|
617
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
618
|
+
if (idFailure)
|
|
619
|
+
return idFailure;
|
|
620
|
+
const groupIdFailure = requireNonEmptyString(itemValue, "groupId", `${itemPath}.groupId`, sourcePath);
|
|
621
|
+
if (groupIdFailure)
|
|
622
|
+
return groupIdFailure;
|
|
623
|
+
if (itemValue.expectedCapacity !== undefined && itemValue.expectedCapacity !== null && !isNonNegativeInteger(itemValue.expectedCapacity)) {
|
|
624
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedCapacity must be a nonnegative integer or null.`, {
|
|
625
|
+
fieldPath: `${itemPath}.expectedCapacity`
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (itemValue.expectedUsedReservation !== undefined &&
|
|
629
|
+
itemValue.expectedUsedReservation !== null &&
|
|
630
|
+
!isNonNegativeInteger(itemValue.expectedUsedReservation)) {
|
|
631
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedUsedReservation must be a nonnegative integer or null.`, {
|
|
632
|
+
fieldPath: `${itemPath}.expectedUsedReservation`
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
if (itemValue.expectedRequiredEvidenceIds !== undefined && !isStringArray(itemValue.expectedRequiredEvidenceIds)) {
|
|
636
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedRequiredEvidenceIds must be an array of strings.`, {
|
|
637
|
+
fieldPath: `${itemPath}.expectedRequiredEvidenceIds`
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (itemValue.expectedOmitted !== undefined && typeof itemValue.expectedOmitted !== "boolean") {
|
|
641
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedOmitted must be a boolean.`, {
|
|
642
|
+
fieldPath: `${itemPath}.expectedOmitted`
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
646
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
647
|
+
fieldPath: `${itemPath}.notes`
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
function validateReadinessExpectation(itemValue, itemPath, sourcePath) {
|
|
653
|
+
if (!isPlainObject(itemValue)) {
|
|
654
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
655
|
+
}
|
|
656
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
657
|
+
if (idFailure)
|
|
658
|
+
return idFailure;
|
|
659
|
+
if (!READINESS_KIND_VALUES.includes(itemValue.kind)) {
|
|
660
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.kind is not valid.`, {
|
|
661
|
+
fieldPath: `${itemPath}.kind`,
|
|
662
|
+
actual: itemValue.kind
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (itemValue.allowedDecisions !== undefined) {
|
|
666
|
+
if (!isStringArray(itemValue.allowedDecisions) || itemValue.allowedDecisions.some((d) => !READINESS_DECISION_VALUES.includes(d))) {
|
|
667
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.allowedDecisions contains an invalid decision value.`, {
|
|
668
|
+
fieldPath: `${itemPath}.allowedDecisions`
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (itemValue.expectedClassification !== undefined && typeof itemValue.expectedClassification !== "string") {
|
|
673
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedClassification must be a string.`, {
|
|
674
|
+
fieldPath: `${itemPath}.expectedClassification`
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
if (itemValue.expectedIssueCodes !== undefined && !isStringArray(itemValue.expectedIssueCodes)) {
|
|
678
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedIssueCodes must be an array of strings.`, {
|
|
679
|
+
fieldPath: `${itemPath}.expectedIssueCodes`
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
if (itemValue.expectedPrimaryIssueCode !== undefined && typeof itemValue.expectedPrimaryIssueCode !== "string") {
|
|
683
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedPrimaryIssueCode must be a string.`, {
|
|
684
|
+
fieldPath: `${itemPath}.expectedPrimaryIssueCode`
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (itemValue.notApplicable !== undefined && typeof itemValue.notApplicable !== "boolean") {
|
|
688
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notApplicable must be a boolean.`, {
|
|
689
|
+
fieldPath: `${itemPath}.notApplicable`
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
693
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
694
|
+
fieldPath: `${itemPath}.notes`
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
function validateCriticalityExpectation(itemValue, itemPath, sourcePath) {
|
|
700
|
+
if (!isPlainObject(itemValue)) {
|
|
701
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
702
|
+
}
|
|
703
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
704
|
+
if (idFailure)
|
|
705
|
+
return idFailure;
|
|
706
|
+
const responsibilityIdFailure = requireNonEmptyString(itemValue, "responsibilityId", `${itemPath}.responsibilityId`, sourcePath);
|
|
707
|
+
if (responsibilityIdFailure)
|
|
708
|
+
return responsibilityIdFailure;
|
|
709
|
+
if (!CRITICALITY_VALUES.includes(itemValue.expectedCriticality)) {
|
|
710
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.expectedCriticality is not valid.`, {
|
|
711
|
+
fieldPath: `${itemPath}.expectedCriticality`,
|
|
712
|
+
actual: itemValue.expectedCriticality
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
if (typeof itemValue.applicable !== "boolean") {
|
|
716
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.applicable must be a boolean.`, {
|
|
717
|
+
fieldPath: `${itemPath}.applicable`
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (typeof itemValue.requiresFullMapping !== "boolean") {
|
|
721
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.requiresFullMapping must be a boolean.`, {
|
|
722
|
+
fieldPath: `${itemPath}.requiresFullMapping`
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
726
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
727
|
+
fieldPath: `${itemPath}.notes`
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
function validateProducerReadinessExpectations(value, sourcePath) {
|
|
733
|
+
const fieldPath = "producerReadinessExpectations";
|
|
734
|
+
if (!isPlainObject(value)) {
|
|
735
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${fieldPath} must be an object.`, { fieldPath });
|
|
736
|
+
}
|
|
737
|
+
const lists = [
|
|
738
|
+
["ownerExpectations", value.ownerExpectations, validateOwnerExpectation],
|
|
739
|
+
["allocationExpectations", value.allocationExpectations, validateAllocationExpectation],
|
|
740
|
+
["readinessExpectations", value.readinessExpectations, validateReadinessExpectation],
|
|
741
|
+
["criticalityExpectations", value.criticalityExpectations, validateCriticalityExpectation]
|
|
742
|
+
];
|
|
743
|
+
const seenIds = new Set();
|
|
744
|
+
for (const [key, listValue, validateItem] of lists) {
|
|
745
|
+
if (listValue === undefined)
|
|
746
|
+
continue;
|
|
747
|
+
if (!Array.isArray(listValue)) {
|
|
748
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${fieldPath}.${key} must be an array.`, {
|
|
749
|
+
fieldPath: `${fieldPath}.${key}`
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
for (let index = 0; index < listValue.length; index += 1) {
|
|
753
|
+
const itemPath = `${fieldPath}.${key}[${index}]`;
|
|
754
|
+
const itemFailure = validateItem(listValue[index], itemPath, sourcePath);
|
|
755
|
+
if (itemFailure)
|
|
756
|
+
return itemFailure;
|
|
757
|
+
const id = listValue[index].expectationId;
|
|
758
|
+
if (seenIds.has(id)) {
|
|
759
|
+
return failure(sourcePath, "DUPLICATE_PRODUCER_READINESS_EXPECTATION_ID", `${itemPath}.expectationId "${id}" is duplicated.`, {
|
|
760
|
+
fieldPath: `${itemPath}.expectationId`,
|
|
761
|
+
actual: id
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
seenIds.add(id);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|