const COMPARATOR_VERSION = 1; const CANONICAL_VERSION = 1; export class ArchitectureDeltaError extends Error { constructor(code, message, details = {}) { super(message); this.name = 'ArchitectureDeltaError'; this.code = code; this.details = details; } } const codepointOrder = (left, right) => (left < right ? -1 : left > right ? 1 : 0); const sorted = (values) => [...values].sort((left, right) => codepointOrder(String(left), String(right))); function canonical(value) { if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`; if (value && typeof value === 'object') { return `{${Object.keys(value).sort(codepointOrder).map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`; } return JSON.stringify(value); } const equal = (left, right) => canonical(left) === canonical(right); function sortedObjects(values) { return [...values].sort((left, right) => codepointOrder(canonical(left), canonical(right))); } function sortedBy(values, keyFor) { return [...values].sort((left, right) => codepointOrder(String(keyFor(left)), String(keyFor(right)))); } function normalizeRepository(repository) { if (!repository) return undefined; return { url: String(repository.url || '').trim().replace(/\.git\/?$/i, '').replace(/\/$/, '').toLowerCase(), revision: String(repository.revision || '').toLowerCase(), }; } function normalizeComponent(component) { return { ...component, ...(Array.isArray(component.sources) ? { sources: sortedObjects(component.sources) } : {}), }; } function normalizeBoundary(boundary) { return { ...boundary, wraps: sorted(boundary.wraps || []) }; } export function canonicalArchitecture(diagram) { const meta = { ...(diagram.meta || {}) }; delete meta.output; if (meta.repository) meta.repository = normalizeRepository(meta.repository); return { schema_version: diagram.schema_version, diagram_type: diagram.diagram_type, meta, ...(diagram.layout ? { layout: diagram.layout } : {}), components: sortedBy((diagram.components || []).map(normalizeComponent), (component) => component.id), boundaries: sortedBy((diagram.boundaries || []).map(normalizeBoundary), boundaryKey), connections: sortedBy(diagram.connections || [], (connection) => connection.id || ''), ...(diagram.cards ? { cards: diagram.cards } : {}), }; } export function canonicalArchitectureJson(diagram) { return canonical(canonicalArchitecture(diagram)); } function fail(code, message, details) { throw new ArchitectureDeltaError(code, message, details); } function requireComparableShape(diagram, side) { if (diagram?.schema_version !== 1) { fail('delta/schema-version-mismatch', `${side} must use schema_version 1.`, { side, path: '/schema_version', actual: diagram?.schema_version }); } if (diagram?.diagram_type !== 'architecture') { fail('delta/type-mismatch', `${side} must use diagram_type architecture.`, { side, path: '/diagram_type', actual: diagram?.diagram_type }); } } function stableIndex(items, collection, side, missingCode = 'delta/stable-id-required') { const index = new Map(); const missing = []; const duplicates = []; (items || []).forEach((item, itemIndex) => { if (!item?.id) missing.push(`/${collection}/${itemIndex}/id`); else if (index.has(item.id)) duplicates.push(item.id); else index.set(item.id, item); }); if (missing.length) { fail(missingCode, `${side} ${collection} require authored stable ids for comparison.`, { side, paths: sorted(missing), supportedFixes: [`add a unique id to every ${collection} item`], }); } if (duplicates.length) { fail('delta/duplicate-stable-id', `${side} ${collection} contain duplicate ids.`, { side, collection, ids: sorted(new Set(duplicates)), supportedFixes: [`make every ${collection} id unique`], }); } return index; } const boundaryKey = (boundary) => `${boundary.kind}\u001f${boundary.label}`; function boundaryIndex(boundaries, side) { const index = new Map(); const ambiguous = []; for (const boundary of boundaries || []) { const key = boundaryKey(boundary); if (index.has(key)) ambiguous.push(`${boundary.kind}:${boundary.label}`); else index.set(key, boundary); } if (ambiguous.length) { fail('delta/boundary-key-ambiguous', `${side} boundary kind + label keys must be unique.`, { side, boundaries: sorted(new Set(ambiguous)), supportedFixes: ['rename one duplicate boundary or add stable boundary ids in a future schema version'], }); } return index; } function normalizedField(item, field) { const value = item?.[field]; if (field === 'sources' && Array.isArray(value)) return sortedObjects(value); if (field === 'wraps' && Array.isArray(value)) return sorted(value); return value; } function fieldChanges(before, after, groups) { const classifications = []; const changedFields = []; for (const [classification, fields] of Object.entries(groups)) { const changed = fields.filter((field) => !equal(normalizedField(before, field), normalizedField(after, field))); if (changed.length) classifications.push(classification); changedFields.push(...changed.map((field) => `/${field}`)); } return { classifications: sorted(classifications), changedFields: sorted(changedFields) }; } const COMPONENT_FIELDS = { semantic: ['type', 'label', 'sublabel', 'tag'], evidence: ['sources'], geometry: ['row', 'col', 'pos', 'size'], }; const CONNECTION_FIELDS = { topology: ['from', 'to'], semantic: ['label', 'variant'], geometry: ['fromSide', 'toSide', 'route', 'via', 'labelAt', 'labelDx', 'labelDy', 'labelSegment', 'width'], }; const BOUNDARY_FIELDS = { scope: ['wraps'], geometry: ['pad'] }; function statusFor(classifications, kind) { if (classifications.some((value) => ['topology', 'semantic', 'scope'].includes(value))) return 'changed'; if (classifications.includes('evidence')) return 'evidence-changed'; if (classifications.includes('geometry')) return kind === 'connection' ? 'rerouted' : kind === 'component' ? 'moved' : 'geometry-changed'; return 'same'; } function compareEntities(baseIndex, headIndex, kind, groups, describe) { const changes = []; const identityClassification = kind === 'connection' ? 'topology' : kind === 'boundary' ? 'scope' : 'semantic'; for (const id of sorted(new Set([...baseIndex.keys(), ...headIndex.keys()]))) { const base = baseIndex.get(id); const head = headIndex.get(id); if (!base) changes.push({ ...describe(id, undefined, head), status: 'added', classifications: [identityClassification], changedFields: [] }); else if (!head) changes.push({ ...describe(id, base, undefined), status: 'removed', classifications: [identityClassification], changedFields: [] }); else { const fields = fieldChanges(base, head, groups); const status = statusFor(fields.classifications, kind); if (status !== 'same') changes.push({ ...describe(id, base, head), status, ...fields }); } } return changes; } function summaryFor(changes, shape) { const summary = Object.fromEntries(shape.map((key) => [key, 0])); for (const change of changes) { const key = change.status.replace(/-([a-z])/g, (_all, letter) => letter.toUpperCase()); if (Object.hasOwn(summary, key)) summary[key] += 1; } return summary; } function presentationChanged(base, head) { const basePresentation = { title: base.meta?.title, subtitle: base.meta?.subtitle, animation: base.meta?.animation, visual_preset: base.meta?.visual_preset, quality_profile: base.meta?.quality_profile, engineering_profile: base.meta?.engineering_profile, legend: base.meta?.legend, views: base.meta?.views, viewBox: base.meta?.viewBox, layout: base.layout, cards: base.cards, }; const headPresentation = { title: head.meta?.title, subtitle: head.meta?.subtitle, animation: head.meta?.animation, visual_preset: head.meta?.visual_preset, quality_profile: head.meta?.quality_profile, engineering_profile: head.meta?.engineering_profile, legend: head.meta?.legend, views: head.meta?.views, viewBox: head.meta?.viewBox, layout: head.layout, cards: head.cards, }; return !equal(basePresentation, headPresentation); } export function compareArchitecture(base, head, evidence = {}) { requireComparableShape(base, 'base'); requireComparableShape(head, 'head'); const baseComponents = stableIndex(base.components, 'components', 'base'); const headComponents = stableIndex(head.components, 'components', 'head'); const shared = sorted([...baseComponents.keys()].filter((id) => headComponents.has(id))); if (!shared.length) { fail('delta/no-shared-component-id', 'The snapshots share no component id, so Archify cannot prove that they describe the same system.', { supportedFixes: ['preserve at least one authored component id across snapshots'], }); } const baseConnections = stableIndex(base.connections, 'connections', 'base', 'delta/relationship-id-required'); const headConnections = stableIndex(head.connections, 'connections', 'head', 'delta/relationship-id-required'); const baseBoundaries = boundaryIndex(base.boundaries, 'base'); const headBoundaries = boundaryIndex(head.boundaries, 'head'); const baseRepository = normalizeRepository(base.meta?.repository); const headRepository = normalizeRepository(head.meta?.repository); if (baseRepository && headRepository && baseRepository.url !== headRepository.url) { fail('delta/repository-mismatch', 'The snapshots name different repositories.', { baseRepository: baseRepository.url, headRepository: headRepository.url, supportedFixes: ['compare snapshots from the same repository or remove repository evidence from both inputs'], }); } const proofLevel = baseRepository && headRepository && evidence.baseVerified && evidence.headVerified && /^[a-f0-9]{40}$/.test(baseRepository.revision) && /^[a-f0-9]{40}$/.test(headRepository.revision) ? 'revision-pinned' : 'authored'; const components = compareEntities(baseComponents, headComponents, 'component', COMPONENT_FIELDS, (id, before, after) => ({ id, baseLabel: before?.label, headLabel: after?.label, })); const connections = compareEntities(baseConnections, headConnections, 'connection', CONNECTION_FIELDS, (id, before, after) => ({ id, ...(before ? { base: { from: before.from, to: before.to, label: before.label || '' } } : {}), ...(after ? { head: { from: after.from, to: after.to, label: after.label || '' } } : {}), })); const boundaries = compareEntities(baseBoundaries, headBoundaries, 'boundary', BOUNDARY_FIELDS, (_key, before, after) => ({ key: `${(after || before).kind}:${(after || before).label}`, kind: (after || before).kind, label: (after || before).label, })); const provenanceChanged = !equal(baseRepository, headRepository); return { schemaVersion: 1, ok: true, command: 'compare', type: 'architecture', comparatorVersion: COMPARATOR_VERSION, canonicalVersion: CANONICAL_VERSION, completeness: 'complete', proofLevel, base: { title: base.meta?.title || '', ...(evidence.baseRawSha256 ? { rawSha256: evidence.baseRawSha256 } : {}), ...(evidence.baseSemanticSha256 ? { semanticSha256: evidence.baseSemanticSha256 } : {}), ...(Number.isInteger(evidence.baseBytes) ? { bytes: evidence.baseBytes } : {}), ...(baseRepository?.revision ? { revision: baseRepository.revision } : {}), }, head: { title: head.meta?.title || '', ...(evidence.headRawSha256 ? { rawSha256: evidence.headRawSha256 } : {}), ...(evidence.headSemanticSha256 ? { semanticSha256: evidence.headSemanticSha256 } : {}), ...(Number.isInteger(evidence.headBytes) ? { bytes: evidence.headBytes } : {}), ...(headRepository?.revision ? { revision: headRepository.revision } : {}), }, summary: { components: summaryFor(components, ['added', 'changed', 'evidenceChanged', 'removed', 'moved']), connections: summaryFor(connections, ['added', 'changed', 'removed', 'rerouted']), boundaries: summaryFor(boundaries, ['added', 'changed', 'removed', 'geometryChanged']), presentationChanged: presentationChanged(base, head), provenanceChanged, }, changes: { components, connections, boundaries }, identity: { components: 'components[].id', connections: 'connections[].id (required)', boundaries: 'boundaries[].kind + boundaries[].label (derived)', }, view: { visualPreset: head.meta?.visual_preset || 'classic' }, limitations: [ 'Authored Architecture IR only; no runtime impact, causality, risk, or mergeability is inferred.', 'Boundary identity is conservatively derived from kind + label.', ], }; } function esc(value) { return String(value ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); } const safeJson = (value) => JSON.stringify(value, null, 2).replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026'); export function extractArchitectureSvg(html) { const match = html.match(//); if (!match) fail('delta/svg-missing', 'A validated Architecture artifact did not contain its primary SVG.'); return match[0]; } export function extractArtifactCss(html) { const match = html.match(/
  • `; }).join('\n') : '
  • No authored architecture changes.
  • '; const baseView = baseHtml ? `` : baseSvg; const headView = headHtml ? `` : headSvg; const html = ` ${esc(receipt.head.title)} Architecture Delta

    ARCHITECTURE DELTA · ${proof}

    See what changed
    before you merge.

    ${esc(receipt.base.title)} → ${esc(receipt.head.title)}

    ${total(receipt.summary, 'added')}ADDED
    ${total(receipt.summary, 'removed')}REMOVED
    ${changed}CHANGED
    + ADD− DEL~ MOD↔ MOVE
    ${deltaSvg}
    Exact authored changes · ${rows.length}
    `; return html.replace(/[ \t]+$/gm, ''); } export function validateArchitectureDeltaHtml(html, receipt) { const failures = []; const rows = architectureDeltaChangeRows(receipt); const deltaMarkup = html.match(/
    ([\s\S]*?)<\/section>/)?.[1] || ''; const svgTags = [...deltaMarkup.matchAll(/<\/?svg\b[^>]*>/g)]; let svgDepth = 0; let svgRoots = 0; let rootStart = -1; let rootEnd = -1; let svgBalanced = true; for (const match of svgTags) { if (match[0].startsWith(' (html.match(new RegExp(`
    ]*data-change-key="${safeKey}"[^>]*>`, 'g'))].map((match) => match[0]); if (rowMatches.length !== 1) failures.push(`expected exactly one change row ${row.key}`); const targets = reviewTargetTags(deltaMarkup, row); if (!targets.length) failures.push(`missing Delta identity ${row.key}`); if (targets.some(({ tag }) => !/\bdata-delta-state="[^"]+"/.test(tag))) failures.push(`missing Delta target state ${row.key}`); const signature = reviewTargetSignature(targets); const expectedSignature = expectedReviewTargetSignature(row); const storedSignature = rowMatches[0]?.match(/\bdata-change-target-signature="([^"]*)"/)?.[1]; if (!signature || signature !== expectedSignature || storedSignature !== expectedSignature) failures.push(`ambiguous Delta target signature ${row.key}`); if (rowMatches[0]?.match(/\bdata-change-index="([^"]+)"/)?.[1] !== String(index)) failures.push(`incorrect change row order ${row.key}`); const primary = primaryReviewTags(deltaMarkup, row); const states = primary.map((tag) => tag.match(/\bdata-delta-state="([^"]+)"/)?.[1]).filter(Boolean).sort(); if (JSON.stringify(states) !== JSON.stringify(reviewPrimaryStates(row).sort())) failures.push(`ambiguous Delta identity ${row.key}`); const classifications = row.classifications.join(','); if (primary.some((tag) => tag.match(/\bdata-delta-classifications="([^"]*)"/)?.[1] !== classifications)) failures.push(`conflicting Delta classification ${row.key}`); } // Before/After embed the complete existing explorer runtime. Validate claims // made by the Delta shell itself, not implementation vocabulary inside an // escaped srcdoc script (for example, "safe scale" in image export code). const deltaShell = html.replace(/]*><\/iframe>/g, ''); if (/\b(?:SAFE|LOW RISK|MERGEABLE|NO IMPACT|VERIFIED PR)\b/i.test(deltaShell)) failures.push('contains a forbidden risk or mergeability claim'); if (/\b(?:NaN|Infinity)\b/.test(html)) failures.push('contains non-finite output'); if (receipt.completeness !== 'complete') failures.push('receipt is not complete'); if (failures.length) fail('delta/artifact-invalid', `Architecture Delta artifact failed validation: ${failures.join('; ')}.`, { failures }); return { ok: true, checksPassed: 10, checkCount: 10 }; }