import crypto from 'node:crypto'; import { readFileSync } from 'node:fs'; export const CONCEPT_STATUSES = new Set(['approved', 'rejected']); // What a concept is actually strong at. Worlds carry a durable visual // identity (their palette/type half is the magnet); compositions carry a // staging or interaction idea (their topology half is the magnet) that can be // dressed in any committed identity; duals fuse both inseparably. Direction // seeds draw world|dual, surface seeds draw composition|dual. export const CONCEPT_STRENGTHS = new Set(['world', 'composition', 'dual']); // Challenger tiers, ordered by translation cost: graphic grammars map to // interface almost directly, instrument languages carry interaction physics, // atmosphere worlds need the largest translation step. Every seed roll draws // one challenger from each tier so at least one directly-usable graphic // system is always on the table. export const WELL_TIERS = ['graphic', 'interaction', 'atmosphere']; const WEB_LEVERAGE_RE = /(?:\b3d\b|\badaptive\b|\banimat(?:e|ed|ion)\b|\bapi\b|\baria\b|\baudio\b|\bautomated?\b|\bbarcode\b|\bbroadcastchannel\b|\bbrowser\b|\bcamera\b|canvas\b|\bcaption\b|\bcollaborat(?:e|ive|ion)\b|\bcompar(?:e|ison)\b|\bcomput(?:e|ed|ation)\b|\bcomputer[- ]vision\b|\bconstraint[- ]solving\b|\bcryptographic?\b|\bcss\b|\bdeep[- ]link(?:ing)?\b|\bdirect manipulation\b|\bdom\b|\bdrag\b|\bfilter\b|\bfocus\b|\bgenerative\b|\bgeolocat(?:e|ed|ion)\b|\bgesture\b|\bgpu\b|\bgraph\b|\bhistory\b|\bindexeddb\b|\binteractive\b|\bintersectionobserver\b|\bkeyboard\b|\blive\b|\blocal\b|\bmicrophone\b|\bmotion\b|\bmultiplayer\b|\bnative\b|\bnotification\b|\boffline\b|\bpersonaliz(?:e|ed|ation)\b|\bplayable\b|\bpointer\b|\bprocedural\b|\bprovenance\b|\breal[- ]?time\b|\bresizeobserver\b|\bresponsive\b|\breveal\b|\bscrub\b|\bsearch\b|\bsearchparams\b|\bsensor\b|\bserver[- ]sent\b|\bservice worker\b|\bshader\b|\bsimulat(?:e|ed|ion|or)\b|\bspatial\b|\bstate\b|\bstream(?:ing)?\b|\bsvg\b|\bsynchroniz(?:e|ed|ation)\b|\btimeline\b|\btouch\b|\burl|\bvideo\b|\bweb(?:gl|socket|vtt)?\b|\bworker\b|\bzoom\b)/i; export const SYSTEM_PREFIXES = [ 'Palette/material:', 'Type/composition:', 'Topology/navigation:', 'Controls/state:', 'Responsive/motion:', ]; const BLAND_FORM_RE = /\b(?:control room|command center|operations center|dispatch desk|review queue|speaker queue|management console|admin console|operator loop|coordination system|tracking system|planning system|software platform|digital platform|operations cockpit|app portal|web portal|data hub|dashboard|workflow|planner|tracker|orchestrator)\b/i; export function normalizeConceptForm(value) { return String(value || '') .normalize('NFKD') .toLowerCase() .replace(/[’‘]/g, "'") .replace(/[^a-z0-9]+/g, ' ') .trim(); } export function validateConceptEntry(concept, { existingForms = new Map() } = {}) { const errors = []; const id = concept?.id || '(unknown)'; if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(concept?.id || '')) { errors.push(`invalid concept id: ${String(concept?.id)}`); } const normalized = normalizeConceptForm(concept?.form); if (!normalized) { errors.push(`concept ${id} needs a form`); } else if (existingForms.has(normalized)) { errors.push(`duplicate concept form: ${id} and ${existingForms.get(normalized)}`); } if (typeof concept?.form !== 'string' || concept.form.trim().length < 40 || concept.form.trim().length > 360 || !concept.form.includes(',')) { errors.push(`concept ${id} must name a form and inherited structure after a comma`); } if (typeof concept?.lineage !== 'string' || concept.lineage.trim().length < 12 || concept.lineage.trim().length > 200) { errors.push(`concept ${id} needs specific lineage metadata of 12–200 characters`); } if (!CONCEPT_STRENGTHS.has(concept?.strength)) { errors.push(`concept ${id} needs a strength of ${[...CONCEPT_STRENGTHS].join(', ')}`); } if (!Array.isArray(concept?.tags) || concept.tags.length !== 3 || concept.tags.some(tag => typeof tag !== 'string' || !tag.trim())) { errors.push(`concept ${id} must have exactly three structural tags`); } if (!Array.isArray(concept?.system) || concept.system.length !== SYSTEM_PREFIXES.length || concept.system.some(rule => typeof rule !== 'string' || rule.trim().length < 12 || rule.trim().length > 180)) { errors.push(`concept ${id} needs system grammar with exactly five rules of 12–180 characters`); } else { const uniqueRules = new Set(concept.system.map(normalizeConceptForm)); if (uniqueRules.size !== SYSTEM_PREFIXES.length) { errors.push(`concept ${id} has duplicate system grammar rules`); } if (concept.system.some((rule, index) => !rule.startsWith(SYSTEM_PREFIXES[index]))) { errors.push(`concept ${id} system grammar must use palette, type, topology, controls, and responsive prefixes in order`); } } if (typeof concept?.spark !== 'string' || concept.spark.trim().length < 80 || concept.spark.trim().length > 320) { errors.push(`concept ${id} needs a vivid creative spark of 80–320 characters`); } if (typeof concept?.webLeverage !== 'string' || concept.webLeverage.trim().length < 20 || concept.webLeverage.trim().length > 240) { errors.push(`concept ${id} needs web leverage of 20–240 characters`); } if (/\b(?:live digital system|shared participatory system) modeled on\b/i.test(concept?.form || '')) { errors.push(`concept ${id} is a generic wrapper around another artifact`); } if (/\b(?:in the style of|styled like|copy of)\b/i.test(concept?.form || '')) { errors.push(`concept ${id} contains imitation language`); } if (BLAND_FORM_RE.test(concept?.form || '')) { errors.push(`concept ${id} is framed as a literal software or operations archetype instead of an inspiring visual world`); } return errors; } // Fingerprint of everything a reviewer judged. Reviews carry this hash so an // approval cannot silently survive a content edit: the validator rejects any // review whose hash no longer matches the concept it points at. export function conceptContentHash(concept) { const payload = [ concept?.form ?? '', concept?.lineage ?? '', JSON.stringify(concept?.tags ?? []), JSON.stringify(concept?.system ?? []), concept?.spark ?? '', concept?.webLeverage ?? '', ].join('\n'); return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12); } export function readConceptCatalog(catalogPath, reviewsPath) { const catalog = JSON.parse(readFileSync(catalogPath, 'utf8')); const reviewData = JSON.parse(readFileSync(reviewsPath, 'utf8')); const reviews = reviewData.reviews || {}; const wellsById = new Map((catalog.wells || []).map(well => [well.id, well])); const concepts = []; for (const family of catalog.families || []) { for (const concept of family.concepts || []) { concepts.push({ ...concept, familyId: family.id, familyLabel: family.label, wellId: family.well || null, wellLabel: wellsById.get(family.well)?.label || null, wellTier: wellsById.get(family.well)?.tier || null, status: reviews[concept.id]?.status || 'pending', review: reviews[concept.id] || null, }); } } return { catalog, reviewData, reviews, concepts }; } export function validateConceptCatalog(catalog, reviewData, { expectedTotal, minimumTotal, requireApprovedMinimum = true, } = {}) { const errors = []; const warnings = []; const familyIds = new Set(); const conceptIds = new Set(); const normalizedForms = new Map(); const concepts = []; if (!Number.isInteger(catalog?.schemaVersion) || catalog.schemaVersion < 7) { errors.push('catalog.schemaVersion must be 7 or newer'); } if (typeof catalog?.catalogVersion !== 'string' || !catalog.catalogVersion.trim()) { errors.push('catalog.catalogVersion must be a non-empty string'); } if (typeof catalog?.qualityBar?.principle !== 'string' || catalog.qualityBar.principle.trim().length < 80) { errors.push('catalog.qualityBar.principle must define the universal creative bar'); } if (!Array.isArray(catalog?.qualityBar?.rejectIf) || catalog.qualityBar.rejectIf.length < 5) { errors.push('catalog.qualityBar.rejectIf must define at least five rejection gates'); } if (!Array.isArray(catalog?.qualityBar?.reviewAxes) || catalog.qualityBar.reviewAxes.length < 8) { errors.push('catalog.qualityBar.reviewAxes must define at least eight review axes'); } if (!Array.isArray(catalog?.families) || catalog.families.length < 3) { errors.push('catalog.families must contain at least three families'); } const wellIds = new Set(); if (!Array.isArray(catalog?.wells) || catalog.wells.length < 5) { errors.push('catalog.wells must define at least five inspiration wells'); } for (const well of catalog?.wells || []) { if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(well.id || '')) { errors.push(`invalid well id: ${String(well.id)}`); } else if (wellIds.has(well.id)) { errors.push(`duplicate well id: ${well.id}`); } wellIds.add(well.id); if (typeof well.label !== 'string' || !well.label.trim()) { errors.push(`well ${well.id || '(unknown)'} needs a label`); } if (typeof well.description !== 'string' || well.description.trim().length < 40) { errors.push(`well ${well.id || '(unknown)'} needs a description of at least 40 characters`); } if (!WELL_TIERS.includes(well.tier)) { errors.push(`well ${well.id || '(unknown)'} needs a tier of ${WELL_TIERS.join(', ')}, got: ${String(well.tier)}`); } } const tiersPresent = new Set((catalog?.wells || []).map(well => well.tier).filter(tier => WELL_TIERS.includes(tier))); for (const tier of WELL_TIERS) { if ((catalog?.wells || []).length > 0 && !tiersPresent.has(tier)) { errors.push(`no well declares the ${tier} tier`); } } const populatedWells = new Set(); for (const family of catalog?.families || []) { if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(family.id || '')) { errors.push(`invalid family id: ${String(family.id)}`); } else if (familyIds.has(family.id)) { errors.push(`duplicate family id: ${family.id}`); } familyIds.add(family.id); if (typeof family.label !== 'string' || !family.label.trim()) { errors.push(`family ${family.id || '(unknown)'} needs a label`); } if (!wellIds.has(family.well)) { errors.push(`family ${family.id || '(unknown)'} must belong to a declared well, got: ${String(family.well)}`); } else { populatedWells.add(family.well); } if (!Array.isArray(family.concepts) || family.concepts.length === 0) { errors.push(`family ${family.id || '(unknown)'} has no concepts`); continue; } for (const concept of family.concepts) { concepts.push(concept); if (conceptIds.has(concept.id)) { errors.push(`duplicate concept id: ${concept.id}`); } errors.push(...validateConceptEntry(concept, { existingForms: normalizedForms })); conceptIds.add(concept.id); const normalized = normalizeConceptForm(concept.form); if (normalized) normalizedForms.set(normalized, concept.id); if (typeof concept.webLeverage === 'string' && !WEB_LEVERAGE_RE.test(concept.webLeverage)) { warnings.push(`concept ${concept.id} web leverage should be checked for a specific browser-native capability`); } } } for (const well of catalog?.wells || []) { if (well.id && !populatedWells.has(well.id)) { errors.push(`well ${well.id} has no families`); } } if (expectedTotal !== undefined && concepts.length !== expectedTotal) { errors.push(`expected ${expectedTotal} concepts, found ${concepts.length}`); } if (minimumTotal !== undefined && concepts.length < minimumTotal) { errors.push(`expected at least ${minimumTotal} concepts, found ${concepts.length}`); } if (!Number.isInteger(reviewData?.schemaVersion) || reviewData.schemaVersion < 2) { errors.push('reviews.schemaVersion must be 2 or newer'); } const conceptsById = new Map(concepts.map(concept => [concept.id, concept])); for (const [id, review] of Object.entries(reviewData?.reviews || {})) { if (!conceptIds.has(id)) errors.push(`review references missing concept: ${id}`); if (!CONCEPT_STATUSES.has(review?.status)) errors.push(`invalid review status for ${id}: ${String(review?.status)}`); if (typeof review?.reviewedBy !== 'string' || !review.reviewedBy.trim()) { errors.push(`review ${id} needs reviewedBy`); } if (typeof review?.reviewedAt !== 'string' || Number.isNaN(Date.parse(review.reviewedAt))) { errors.push(`review ${id} needs an ISO reviewedAt timestamp`); } if (typeof review?.formHash !== 'string' || !review.formHash.trim()) { errors.push(`review ${id} needs a formHash of the reviewed content`); } else if (conceptsById.has(id) && review.formHash !== conceptContentHash(conceptsById.get(id))) { errors.push(`review ${id} is stale: concept content changed since it was reviewed; reset or re-review it`); } if (review?.note !== undefined && (typeof review.note !== 'string' || !review.note.trim() || review.note.length > 500)) { errors.push(`review ${id} note must be a non-empty string of 500 characters or fewer`); } // Rating grades how strong an approved concept is (3 exceptional, 2 solid, // 1 marginal keep). Optional, approved-only, and read as a calibration // signal for future authoring rounds. if (review?.rating !== undefined) { if (![1, 2, 3].includes(review.rating)) { errors.push(`review ${id} rating must be 1, 2, or 3`); } else if (review.status !== 'approved') { errors.push(`review ${id} rating only applies to approved concepts`); } } } const wellTierById = new Map((catalog?.wells || []).map(well => [well.id, well.tier])); const approved = concepts.filter(concept => reviewData?.reviews?.[concept.id]?.status === 'approved'); const approvedTiers = new Set( (catalog?.families || []) .filter(family => family.concepts?.some(concept => reviewData?.reviews?.[concept.id]?.status === 'approved')) .map(family => wellTierById.get(family.well)) .filter(tier => WELL_TIERS.includes(tier)) ); if (requireApprovedMinimum && approved.length < 3) errors.push('at least three concepts must be approved'); if (requireApprovedMinimum && approvedTiers.size < WELL_TIERS.length) { errors.push('approved concepts must cover every challenger tier'); } return { errors, warnings, stats: { wells: wellIds.size, families: familyIds.size, concepts: concepts.length, approved: approved.length, pending: concepts.length - Object.keys(reviewData?.reviews || {}).length, rejected: Object.values(reviewData?.reviews || {}).filter(review => review?.status === 'rejected').length, }, }; } export function approvedPoolRevision(concepts) { const payload = concepts .filter(concept => concept.status === 'approved') .map(concept => `${concept.familyId}:${concept.id}:${concept.strength}:${concept.form}:${concept.spark}:${JSON.stringify(concept.system)}:${concept.webLeverage}`) .sort() .join('\n'); return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12); } export function deterministicRank(items, input, idFor = item => item.id) { return [...items].sort((a, b) => { const scoreA = crypto.createHash('sha256').update(`${input}:${idFor(a)}`).digest('hex'); const scoreB = crypto.createHash('sha256').update(`${input}:${idFor(b)}`).digest('hex'); return scoreB.localeCompare(scoreA) || idFor(a).localeCompare(idFor(b)); }); }