<!-- SAGA-SOLID · part 19 -->
c must be an object'}]};
  }

  // Security: forbidden keys
  var fk = hasForbiddenKeys(spec);
  if(fk) {
    return {ok: false, errors: [{field: fk.path, reason: fk.reason}]};
  }

  // Name
  if(!isValidString(spec.name, LIMITS.MAX_NAME_LENGTH) || !spec.name.trim()) {
    errors.push({field: 'name', reason: 'name required, string, max ' + LIMITS.MAX_NAME_LENGTH});
  }

  // Creator metadata
  var creatorCheck = validateCreator(spec.creator);
  if(!creatorCheck.ok) {
    errors = errors.concat(creatorCheck.errors);
  }

  // Description (optional)
  if(spec.description !== undefined && !isValidString(spec.description, LIMITS.MAX_DESCRIPTION_LENGTH)) {
    errors.push({field: 'description', reason: 'max ' + LIMITS.MAX_DESCRIPTION_LENGTH + ' chars'});
  }

  // Tags (optional)
  if(spec.tags !== undefined) {
    if(!Array.isArray(spec.tags) || spec.tags.length > LIMITS.MAX_TAGS_PER_BLUEPRINT) {
      errors.push({field: 'tags', reason: 'array, max ' + LIMITS.MAX_TAGS_PER_BLUEPRINT});
    } else {
      for(var ti = 0; ti < spec.tags.length; ti++) {
        if(!isValidString(spec.tags[ti], LIMITS.MAX_TAG_LENGTH)) {
          errors.push({field: 'tags[' + ti + ']', reason: 'max ' + LIMITS.MAX_TAG_LENGTH + ' chars'});
          break;
        }
        if(isExecutableValue(spec.tags[ti])) {
          errors.push({field: 'tags[' + ti + ']', reason: 'executable content rejected'});
          break;
        }
      }
    }
  }

  // Components
  if(!Array.isArray(spec.components) || spec.components.length === 0) {
    errors.push({field: 'components', reason: 'non-empty array required'});
  } else if(spec.components.length > LIMITS.MAX_COMPONENTS_PER_BLUEPRINT) {
    errors.push({field: 'components', reason: 'max ' + LIMITS.MAX_COMPONENTS_PER_BLUEPRINT + ' components'});
  } else {
    var compResult = validateComponents(spec.components);
    errors = errors.concat(compResult.errors);
  }

  // Check for executable strings in name/description
  if(spec.name && isExecutableValue(spec.name)) {
    errors.push({field: 'name', reason: 'executable content rejected'});
  }
  if(spec.description && isExecutableValue(spec.description)) {
    errors.push({field: 'description', reason: 'executable content rejected'});
  }

  if(errors.length > 0) {
    return {ok: false, errors: errors};
  }

  // Build the blueprint object
  var bp = {
    blueprintId: generateId('bp'),
    version: 1,
    name: spec.name.trim(),
    creator: creatorCheck.creator, // normalized
    description: spec.description ? spec.description.trim() : '',
    tags: spec.tags ? spec.tags.slice() : [],
    components: compResult.components, // validated + normalized
    bounds: calculateBounds(compResult.components),
    complexity: compResult.complexity,
    lightCount: compResult.lightCount,
    createdAt: Date.now(),
  };

  // Final bounds check
  var bb = bp.bounds;
  if(bb.sizeX > LIMITS.MAX_BLUEPRINT_DIM || bb.sizeY > LIMITS.MAX_BLUEPRINT_DIM || bb.sizeZ > LIMITS.MAX_BLUEPRINT_DIM) {
    return {ok: false, errors: [{field: 'bounds', reason: 'blueprint exceeds max dimension ' + LIMITS.MAX_BLUEPRINT_DIM}]};
  }
  if(bp.complexity > LIMITS.MAX_COMPLEXITY_BUDGET) {
    return {ok: false, errors: [{field: 'complexity', reason: 'exceeds budget ' + LIMITS.MAX_COMPLEXITY_BUDGET}]};
  }
  if(bp.lightCount > LIMITS.MAX_LIGHTS_PER_BLUEPRINT) {
    return {ok: false, errors: [{field: 'lightCount', reason: 'max ' + LIMITS.MAX_LIGHTS_PER_BLUEPRINT + ' lights'}]};
  }

  return {ok: true, blueprint: bp};
}

/* ============================================================
   CREATOR/SOURCE METADATA
   Distinguishes human and authorized AI identities
   ============================================================ */
function validateCreator(creator) {
  if(!creator || typeof creator !== 'object') {
    return {ok: false, errors: [{field: 'creator', reason: 'creator object required'}]};
  }
  var fk = hasForbiddenKeys(creator, 'creator');
  if(fk) {
    return {ok: false, errors: [{field: fk.path, reason: fk.reason}]};
  }

  var errors = [];

  // type: must be 'human' or 'ai'
  if(creator.type !== 'human' && creator.type !== 'ai') {
    errors.push({field: 'creator.type', reason: 'must be "human" or "ai"'});
  }

  // identity: string identifier
  if(!isValidString(creator.identity, LIMITS.MAX_STRING_FIELD) || !creator.identity.trim()) {
    errors.push({field: 'creator.identity', reason: 'identity string required'});
  } else if(isExecutableValue(creator.identity)) {
    errors.push({field: 'creator.identity', reason: 'executable content rejected'});
  }

  // For AI: agentId required
  if(creator.type === 'ai') {
    if(!isValidString(creator.agentId, LIMITS.MAX_STRING_FIELD) || !creator.agentId.trim()) {
      errors.push({field: 'creator.agentId', reason: 'agentId required for AI creators'});
    }
  }

  if(errors.length > 0) {
    return {ok: false, errors: errors};
  }

  return {
    ok: true,
    creator: {
      type: cre