{"record":{"id":"a1b1938e087902c8","repo":"affaan-m/ECC","slug":"nasiko-manifest-must-contain-exactly-one-oci-layer","errorCode":null,"errorMessage":"Nasiko manifest must contain exactly one OCI layer.","messagePattern":"Nasiko manifest must contain exactly one OCI layer\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/nasiko-release.js","lineNumber":64,"sourceCode":"  if (!qualification) throw new Error(`Nasiko ${version} is not qualified for ${normalized.os}/${normalized.arch}.`);\n  return { version, ...normalized, ...qualification, license: LICENSE, sourceUrl: SOURCE_URL };\n}\n\nfunction digestBytes(bytes) {\n  return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;\n}\n\nfunction assertDigest(bytes, expectedDigest, label) {\n  if (!SHA256_PATTERN.test(expectedDigest)) throw new Error(`${label} has an invalid expected digest.`);\n  const actual = digestBytes(bytes);\n  if (actual !== expectedDigest) throw new Error(`${label} digest mismatch: expected ${expectedDigest}, got ${actual}.`);\n}\n\nfunction validateManifest(bytes) {\n  let manifest;\n  try { manifest = JSON.parse(bytes.toString('utf8')); } catch (_error) { throw new Error('Nasiko manifest is not valid JSON.'); }\n  if (manifest.schemaVersion !== 2 || !Array.isArray(manifest.layers) || manifest.layers.length !== 1) {\n    throw new Error('Nasiko manifest must contain exactly one OCI layer.');\n  }\n  const layer = manifest.layers[0];\n  if (layer.mediaType !== 'application/gzip' || !SHA256_PATTERN.test(layer.digest)) {\n    throw new Error('Nasiko manifest layer is not a qualified gzip artifact.');\n  }\n  if (!Number.isSafeInteger(layer.size) || layer.size <= 0 || layer.size > MAX_ARCHIVE_BYTES) {\n    throw new Error('Nasiko manifest layer size is outside the allowed range.');\n  }\n  return { digest: layer.digest, size: layer.size };\n}\n\nfunction readTarString(block, offset, length) {\n  return block.subarray(offset, offset + length).toString('utf8').replace(/\\0.*$/, '');\n}\n\nfunction extractQualifiedTarGzip(archiveBytes, expectedName) {\n  let tar;\n  try { tar = zlib.gunzipSync(archiveBytes, { maxOutputLength: MAX_BINARY_BYTES + 2048 }); }","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/affaan-m/ECC/blob/06c5e118c4d3e6c3b7f9445f973a2194c82de193/scripts/lib/nasiko-release.js#L46-L82","documentation":"validateManifest in scripts/lib/nasiko-release.js enforces the exact OCI shape it is willing to extract from: schemaVersion must be 2 and layers must be an array of length exactly 1. Anything else - schemaVersion 1 manifests, OCI image indexes (which have 'manifests' instead of 'layers'), or multi-layer images - is rejected because the qualified release is a single gzip layer carrying one binary.","triggerScenarios":"The manifest bytes (already digest-verified) are an OCI index or schemaVersion 1 manifest, or a multi-layer image. Concretely: manifest.schemaVersion !== 2, Array.isArray(manifest.layers) === false, or manifest.layers.length !== 1.","commonSituations":"Upstream re-published the release as a multi-arch index while the pinned digest was updated inconsistently; a mirror rewrote the manifest; the qualification table was hand-edited with a digest of the index instead of the single-platform manifest.","solutions":["Pin and verify the per-platform manifest (schemaVersion 2 with one layer), not the top-level index digest, when qualifying releases","Update the ECC checkout so QUALIFIED_RELEASES carries the digest of the correct single-layer manifest","Report upstream if the registry content changed for a pinned tag"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await installNasiko({ version: 'v0.1.0' });\n} catch (error) {\n  if (/exactly one OCI layer/.test(String(error.message))) {\n    // The served manifest is an index/schema-1/multi-layer doc. Re-qualify the\n    // release pinning the single-platform schemaVersion 2 manifest digest.\n  }\n  throw error;\n}","preventionTips":["When qualifying releases, pin the per-platform manifest digest, not the index digest","Automate a smoke install in CI for every new release so manifest-shape regressions surface immediately","Validate published manifests with a schema check before tagging a release"],"tags":["oci","manifest","validation"],"backgroundTag":"oci-manifest-invalid","analyzedSha":"06c5e118c4d3e6c3b7f9445f973a2194c82de193","analyzedAt":"2026-08-18T11:27:13.915Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}