affaan-m/ECC · error
Nasiko manifest must contain exactly one OCI layer.
Error message
Nasiko manifest must contain exactly one OCI layer.
What it means
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.
Source
Thrown at scripts/lib/nasiko-release.js:64
if (!qualification) throw new Error(`Nasiko ${version} is not qualified for ${normalized.os}/${normalized.arch}.`);
return { version, ...normalized, ...qualification, license: LICENSE, sourceUrl: SOURCE_URL };
}
function digestBytes(bytes) {
return `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
}
function assertDigest(bytes, expectedDigest, label) {
if (!SHA256_PATTERN.test(expectedDigest)) throw new Error(`${label} has an invalid expected digest.`);
const actual = digestBytes(bytes);
if (actual !== expectedDigest) throw new Error(`${label} digest mismatch: expected ${expectedDigest}, got ${actual}.`);
}
function validateManifest(bytes) {
let manifest;
try { manifest = JSON.parse(bytes.toString('utf8')); } catch (_error) { throw new Error('Nasiko manifest is not valid JSON.'); }
if (manifest.schemaVersion !== 2 || !Array.isArray(manifest.layers) || manifest.layers.length !== 1) {
throw new Error('Nasiko manifest must contain exactly one OCI layer.');
}
const layer = manifest.layers[0];
if (layer.mediaType !== 'application/gzip' || !SHA256_PATTERN.test(layer.digest)) {
throw new Error('Nasiko manifest layer is not a qualified gzip artifact.');
}
if (!Number.isSafeInteger(layer.size) || layer.size <= 0 || layer.size > MAX_ARCHIVE_BYTES) {
throw new Error('Nasiko manifest layer size is outside the allowed range.');
}
return { digest: layer.digest, size: layer.size };
}
function readTarString(block, offset, length) {
return block.subarray(offset, offset + length).toString('utf8').replace(/\0.*$/, '');
}
function extractQualifiedTarGzip(archiveBytes, expectedName) {
let tar;
try { tar = zlib.gunzipSync(archiveBytes, { maxOutputLength: MAX_BINARY_BYTES + 2048 }); }View on GitHub (pinned to 06c5e118c4)
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
Defensive patterns
Strategy: try-catch
Try / catch
try {
await installNasiko({ version: 'v0.1.0' });
} catch (error) {
if (/exactly one OCI layer/.test(String(error.message))) {
// The served manifest is an index/schema-1/multi-layer doc. Re-qualify the
// release pinning the single-platform schemaVersion 2 manifest digest.
}
throw error;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Nasiko manifest layer size is outside the allowed range.
- Install module ${moduleId} has invalid targets; expected an
- Install module ${moduleId} has unsupported targets: ${unsupp
- Nasiko manifest is not valid JSON.
- Nasiko manifest layer is not a qualified gzip artifact.
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/a1b1938e087902c8.
Report an issue: GitHub.