affaan-m/ECC · error · Error
Nasiko manifest must contain exactly one OCI layer.
Error message
Nasiko manifest must contain exactly one OCI layer.
What it means
validateManifest enforces that the manifest declares schemaVersion 2 and contains a layers array with exactly one entry, matching this library's single-layer release artifact contract. The error is thrown when any of those structural conditions fail. It protects installNasiko from multi-layer or malformed manifests it cannot verify.
Solutions
- Verify you resolved the correct release artifact, not a generic container image with multiple layers.
- Check manifest.schemaVersion is 2 and console.log(manifest.layers.length) to see what was actually published.
- Refresh the manifest reference (digest-pinned tag) to pick up the current published artifact.
- If you must install from a multi-layer image, select and verify the intended layer manually instead of using this library path.
Example fix
// before
validateManifest(fs.readFileSync('image-manifest.json'));
// after
const manifest = JSON.parse(fs.readFileSync('image-manifest.json', 'utf8'));
if (manifest.schemaVersion !== 2 || manifest.layers?.length !== 1) {
throw new Error(`not a Nasiko release manifest: schema=${manifest.schemaVersion}, layers=${manifest.layers?.length}`);
}
validateManifest(fs.readFileSync('image-manifest.json')); Defensive patterns
Strategy: validation
Validate before calling
const m = JSON.parse(manifestBytes.toString('utf8'));
if (m.schemaVersion !== 2 || !Array.isArray(m.layers) || m.layers.length !== 1) throw new Error('not a single-layer OCI v2 manifest'); Type guard
const isSingleLayerManifest = (m) => m?.schemaVersion === 2 && Array.isArray(m.layers) && m.layers.length === 1;
Try / catch
try { installNasiko(opts); } catch (e) { if (e.message.includes('exactly one OCI layer')) console.error('wrong artifact selected; need the single-layer Nasiko release'); throw e; } Prevention
- Digest-pin the release reference so you always resolve the intended artifact.
- Validate manifest shape right after fetch, before install.
- Keep release publishing to exactly one layer per this library's contract.
When it happens
Trigger: Passing a manifest with schemaVersion other than 2, a missing/non-array layers field, an empty layers array, or a multi-layer image manifest (e.g. a standard multi-arch index or an image with several layers) to validateManifest.
Common situations: Pointing the installer at a general-purpose container image instead of the purpose-built Nasiko artifact, a publisher restructured the release to multiple layers, or an old manifest schema (v1) is still cached.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- -32602
- artifact path must be a non-empty relative path
- assets must be a nonempty list
- built an invalid spec
- Canonical session snapshot must be an object
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a1b1938e087902c8.
Report an issue: GitHub.
Appendix: 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 readTarOctal(block, offset, length) {
const field = block.subarray(offset, offset + length).toString('ascii');
const match = /^ *([0-7]+)[ \0]*$/.exec(field);View on GitHub (pinned to 8321021c54)