arkgate 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  // GENERATED from src/kernel/analysis.ts by scripts/generate-analysis-engine.mjs.
2
2
  // Do not edit this file directly.
3
- function w(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function E(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(E).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(n=>`${JSON.stringify(n)}:${E(t[n])}`).join(",")}}`}var ye=new Map;function he(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function W(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function De(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function D(e){let t=ye.get(e);if(t)return t;let n=W(e),r=De(n),i="",a=0;for(let l=0;l<n.length;l+=1){let p=n[l];p==="\\"&&l+1<n.length?(i+=he(n[l+1]),l+=1):p==="*"?n[l+1]==="*"?n[l+2]==="/"?(i+="(?:.*/)?",l+=2):(i+=".*",l+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=he(p)}let o=new RegExp(`^${i}$`);return ye.set(e,o),o}function J(e){let t=W(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function L(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(o=>D(o).test(n))){for(let o of a.patterns??[])if(D(o).test(n)){let l=J(o);l>i&&(i=l,r=a.name)}}return r}function me(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function Fe(e){let t=new Set;for(let n of e??[]){let i=W(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let o=i[a];if((o==="**"||o==="*")&&a>0){let l=i[a-1];l&&!l.includes("*")&&!l.includes("{")&&!l.includes("}")&&t.add(l)}}}return[...t]}function je(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return Fe(r?.patterns)}function H(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,o=r?.toPath;if(!a||!o)continue;let l=je(i,t,r?.layers);if(l.length===0)continue;let p=me(a,l),u=me(o,l);if(!p||!u)continue;if(p!==u)return i;continue}if(t!==n)return i}}var Z="1.0";var V=class extends Error{issues;source;constructor(t,n){super(`Invalid architecture change map (${t}):
3
+ function $(e){let t=2166136261;for(let n=0;n<e.length;n+=1)t^=e.charCodeAt(n),t=Math.imul(t,16777619);return`fnv1a-${(t>>>0).toString(16).padStart(8,"0")}`}function x(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(x).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(n=>`${JSON.stringify(n)}:${x(t[n])}`).join(",")}}`}var Ae=new Map;function be(e){return/[.*+?^${}()|[\]\\]/.test(e)?`\\${e}`:e}function X(e){let t="";for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"&&n+1<e.length){let i=e[n+1];if("*?{}[],".includes(i)||i==="\\"){t+="\\"+i,n+=1;continue}t+="/";continue}t+=r}return t}function He(e){let t=0;for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="\\"){n+=1;continue}if(r==="{")t+=1;else if(r==="}"&&(t-=1,t<0))return!1}return t===0}function D(e){let t=Ae.get(e);if(t)return t;let n=X(e),r=He(n),i="",a=0;for(let l=0;l<n.length;l+=1){let p=n[l];p==="\\"&&l+1<n.length?(i+=be(n[l+1]),l+=1):p==="*"?n[l+1]==="*"?n[l+2]==="/"?(i+="(?:.*/)?",l+=2):(i+=".*",l+=1):i+="[^/]*":p==="?"?i+="[^/]":p==="{"&&r?(i+="(?:",a+=1):p==="}"&&r&&a>0?(i+=")",a-=1):p===","&&r&&a>0?i+="|":i+=be(p)}let o=new RegExp(`^${i}$`);return Ae.set(e,o),o}function Q(e){let t=X(String(e)),r=t.split("*")[0].split("/").filter(Boolean).length,i=t.replace(/\*/g,"").length;return r*1e4+i}function _(e,t){let n=String(e).split(/[/\\]/).join("/"),r,i=-1;for(let a of t??[])if(!(a.exclude??[]).some(o=>D(o).test(n))){for(let o of a.patterns??[])if(D(o).test(n)){let l=Q(o);l>i&&(i=l,r=a.name)}}return r}function Ce(e,t){if(!t?.length)return;let n=String(e).split(/[/\\]/).filter(Boolean),r=new Set(t.map(i=>String(i).toLowerCase()));for(let i=0;i<n.length-1;i+=1)if(r.has(n[i].toLowerCase()))return`${n[i]}/${n[i+1]}`}function Ge(e){let t=new Set;for(let n of e??[]){let i=X(String(n)).split("/").filter(Boolean);for(let a=0;a<i.length;a+=1){let o=i[a];if((o==="**"||o==="*")&&a>0){let l=i[a-1];l&&!l.includes("*")&&!l.includes("{")&&!l.includes("}")&&t.add(l)}}}return[...t]}function Ue(e,t,n){if(Array.isArray(e.sliceFolders)&&e.sliceFolders.length>0)return e.sliceFolders.filter(i=>typeof i=="string"&&i.length>0);let r=(n??[]).find(i=>i.name===t);return Ge(r?.patterns)}function G(e,t,n,r){for(let i of e??[])if(!(i.from!==t||i.to!==n)&&i.allowed===!1){if(i.peerIsolation){let a=r?.fromPath,o=r?.toPath;if(!a||!o)continue;let l=Ue(i,t,r?.layers);if(l.length===0)continue;let p=Ce(a,l),u=Ce(o,l);if(!p||!u)continue;if(p!==u)return i;continue}if(t!==n)return i}}var ee="1.0";var U=class extends Error{issues;source;constructor(t,n){super(`Invalid architecture change map (${t}):
4
4
  ${n.map(r=>`- ${r.path}: ${r.message}`).join(`
5
- `)}`),this.name="ArchitectureChangeMapValidationError",this.source=t,this.issues=n}};function X(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function Q(e,t,n,r){for(let i of Object.keys(e))t.includes(i)||r.push({path:`${n}.${i}`,message:"unknown field"})}function O(e,t,n,r){let i=e[t];if(typeof i=="string"&&i.length>0)return i;r.push({path:`${n}.${t}`,message:"must be a non-empty string"})}function He(e){let t=e.replace(/\\/g,"/");if(!t||t.startsWith("/")||/^[A-Za-z]:\//.test(t)||t.includes("\0"))return;let n=[];for(let i of t.split("/"))if(!(!i||i==="."))if(i===".."){if(n.length===0)return;n.pop()}else n.push(i);let r=n.join("/");return r&&r===t?r:void 0}function Ve(e,t,n="architecture change map"){let r=[];if(!X(e))throw new V(n,[{path:"$",message:"must be an object"}]);Q(e,["$schema","schemaVersion","files","dependencies"],"$",r);let i=O(e,"$schema","$",r),a=O(e,"schemaVersion","$",r);a&&a!==Z&&r.push({path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(a)}; expected ${Z}`});let o=new Set(t.layers.map(d=>d.name)),l=[],p=new Set;!Array.isArray(e.files)||e.files.length===0?r.push({path:"$.files",message:"must be a non-empty array"}):e.files.forEach((d,f)=>{let y=`$.files[${f}]`;if(!X(d)){r.push({path:y,message:"must be an object"});return}Q(d,["path","operation","layer"],y,r);let m=O(d,"path",y,r),A=O(d,"operation",y,r),C=O(d,"layer",y,r),k=m?He(m):void 0;m&&!k&&r.push({path:`${y}.path`,message:"must be a canonical project-relative path"}),A&&!["create","update","delete"].includes(A)&&r.push({path:`${y}.operation`,message:"must be create, update, or delete"}),C&&!o.has(C)&&r.push({path:`${y}.layer`,message:`references unknown layer ${JSON.stringify(C)}`}),k&&p.has(k)&&r.push({path:`${y}.path`,message:`duplicates planned path ${k}`}),k&&p.add(k);let Y=k?L(k,t.layers):void 0;k&&C&&o.has(C)&&Y!==C&&r.push({path:`${y}.layer`,message:Y?`${k} resolves to ${Y}, not ${C}`:`${k} is not assigned to an architecture layer`}),k&&A&&["create","update","delete"].includes(A)&&C&&l.push({path:k,operation:A,layer:C})});let u=[],h=new Map(l.map(d=>[d.path,d.operation])),g=new Set,s=e.dependencies??[];if(Array.isArray(s)?s.forEach((d,f)=>{let y=`$.dependencies[${f}]`;if(!X(d)){r.push({path:y,message:"must be an object"});return}Q(d,["from","to"],y,r);let m=O(d,"from",y,r),A=O(d,"to",y,r);m&&!p.has(m)&&r.push({path:`${y}.from`,message:`must reference a planned file path: ${m}`}),A&&!p.has(A)&&r.push({path:`${y}.to`,message:`must reference a planned file path: ${A}`}),m&&h.get(m)==="delete"&&r.push({path:`${y}.from`,message:`cannot depend from deleted file ${m}`}),A&&h.get(A)==="delete"&&r.push({path:`${y}.to`,message:`cannot depend on deleted file ${A}`}),m&&A&&m===A&&r.push({path:y,message:"must not declare a self dependency"});let C=m&&A?`${m}\0${A}`:void 0;C&&g.has(C)&&r.push({path:y,message:`duplicates dependency ${m} -> ${A}`}),C&&g.add(C),m&&A&&u.push({from:m,to:A})}):r.push({path:"$.dependencies",message:"must be an array"}),r.length>0)throw new V(n,r);let c={$schema:i,schemaVersion:Z,files:l.sort((d,f)=>d.path.localeCompare(f.path)),dependencies:u.sort((d,f)=>d.from.localeCompare(f.from)||d.to.localeCompare(f.to))};return{map:c,hash:w(E(c))}}function S(e){return`${e.from}->${e.to}`}function F(e,t,n,r="dependency"){return{id:`${e}:${r}:${S(t)}`,classification:e,subject:"dependency",from:t.from,to:t.to,message:n,...e==="missing"?{nextAction:`Add the planned dependency ${S(t)} to the candidate, then preflight again.`}:e==="contradictory"?{nextAction:`Replace the reverse dependency with ${S(t)}, then preflight again.`}:e==="unplanned"?{nextAction:`Remove the unplanned dependency ${S(t)} from the candidate, then preflight again.`}:{}}}function ee(e){let t=[],n=new Map(e.changeMap.map.files.map(s=>[s.path,s])),r=new Map(e.changes.map(s=>[s.path,s])),i=new Map(e.changeMap.map.dependencies.map(s=>[S(s),s])),a=new Map(e.baseDependencies.map(s=>[S(s),s])),o=new Map(e.candidateDependencies.map(s=>[S(s),s]));for(let s of[...n.values()].sort((c,d)=>c.path.localeCompare(d.path))){let c=r.get(s.path);c?c.operation!==s.operation?t.push({id:`contradictory:file:${s.path}`,classification:"contradictory",subject:"file",path:s.path,expectedOperation:s.operation,actualOperation:c.operation,message:`${s.path} was planned as ${s.operation} but the actual operation is ${c.operation}.`,nextAction:`Change ${s.path} to the planned ${s.operation} operation, then preflight again.`}):t.push({id:`satisfied:file:${s.path}`,classification:"satisfied",subject:"file",path:s.path,expectedOperation:s.operation,actualOperation:c.operation,message:`${s.path} matches the planned ${s.operation} operation.`}):t.push({id:`missing:file:${s.path}`,classification:"missing",subject:"file",path:s.path,expectedOperation:s.operation,message:`${s.path} was planned as ${s.operation} but is absent from the actual change.`,nextAction:`${s.operation[0].toUpperCase()}${s.operation.slice(1)} ${s.path} in the complete change set, then preflight again.`})}for(let s of[...r.values()].sort((c,d)=>c.path.localeCompare(d.path)))n.has(s.path)||t.push({id:`unplanned:file:${s.path}`,classification:"unplanned",subject:"file",path:s.path,actualOperation:s.operation,message:`${s.path} has an unplanned ${s.operation} operation.`,nextAction:`Remove ${s.path} from the change set, then preflight again.`});let l=new Set;for(let s of[...i.values()].sort((c,d)=>S(c).localeCompare(S(d)))){if(o.has(S(s))){t.push(F("satisfied",s,`${s.from} -> ${s.to} exists in the candidate architecture.`));continue}let c={from:s.to,to:s.from};o.has(S(c))?(l.add(S(c)),t.push(F("contradictory",s,`${s.from} -> ${s.to} was planned, but the candidate contains the reverse edge.`))):t.push(F("missing",s,`${s.from} -> ${s.to} is absent from the candidate architecture.`))}let p=new Set([...n.keys(),...r.keys()]);for(let[s,c]of[...o].sort(([d],[f])=>d.localeCompare(f)))a.has(s)||i.has(s)||l.has(s)||!p.has(c.from)&&!p.has(c.to)||t.push({...F("unplanned",c,`${c.from} -> ${c.to} was added without a matching planned dependency.`,"dependency-added"),actualOperation:"added"});let u=new Set(e.changeMap.map.files.filter(s=>s.operation==="delete").map(s=>s.path));for(let[s,c]of[...a].sort(([d],[f])=>d.localeCompare(f)))o.has(s)||u.has(c.from)||u.has(c.to)||!p.has(c.from)&&!p.has(c.to)||t.push({...F("unplanned",c,`${c.from} -> ${c.to} was removed without a planned file deletion.`,"dependency-removed"),actualOperation:"removed"});let h={satisfied:0,missing:1,contradictory:2,unplanned:3};t.sort((s,c)=>h[s.classification]-h[c.classification]||(s.subject===c.subject?0:s.subject==="file"?-1:1)||s.id.localeCompare(c.id));let g={satisfied:t.filter(s=>s.classification==="satisfied").length,missing:t.filter(s=>s.classification==="missing").length,contradictory:t.filter(s=>s.classification==="contradictory").length,unplanned:t.filter(s=>s.classification==="unplanned").length};return{schemaVersion:"1.0",readOnly:!0,changeMapHash:e.changeMap.hash,structurallyConverged:g.missing===0&&g.contradictory===0&&g.unplanned===0,behavioralCompletion:"not-evaluated",summary:g,findings:t}}function _(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function Ce(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Ae(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function Ie(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function te(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ne(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):te(t,r)}catch{a=void 0}return!!a?.declarations?.some(o=>o.getSourceFile().fileName===n.fileName)}function re(e,t){let n,r=[],i=(o,l,p,u=!1)=>r.push({specifier:p,kind:l,line:Ce(t,o),typeOnly:u,unresolved:p===void 0,node:o}),a=o=>{if(e.isImportDeclaration(o))i(o,"import",_(e,o.moduleSpecifier),Ae(e,o));else if(e.isExportDeclaration(o)&&o.moduleSpecifier)i(o,"export",_(e,o.moduleSpecifier),Ae(e,o));else if(e.isImportEqualsDeclaration(o)&&e.isExternalModuleReference(o.moduleReference))i(o,"require",_(e,o.moduleReference.expression));else if(e.isCallExpression(o)){let l=o.expression.kind===e.SyntaxKind.ImportKeyword,u=e.isIdentifier(o.expression)&&o.expression.text==="require"&&!ne(e,n??(n=Ie(e,t)),t,o.expression);(l||u)&&i(o,u?"require":"dynamic-import",_(e,o.arguments[0]))}e.forEachChild(o,a)};return a(t),r}function Ge(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=_(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function Ue(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function be(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function ie(e,t,n){if(n.length===0)return[];let r=new Set(n),i=Ie(e,t),a=new Map,o=new Set;for(let s of t.statements)if(e.isVariableStatement(s))for(let c of s.declarationList.declarations)e.isIdentifier(c.name)&&o.add(c.name.text);let l=s=>{let c=Ge(e,s);if(!c)return;let d=te(i,c.root),f=d?a.get(d):void 0;return f?[...f,...c.segments.slice(1)]:ne(e,i,t,c.root)||o.has(c.root.text)?void 0:c.segments};for(let s of t.statements)if(e.isVariableStatement(s))for(let c of s.declarationList.declarations){if(!c.initializer||!e.isIdentifier(c.name))continue;let d=l(c.initializer),f=te(i,c.name);!d||!f||a.set(f,d)}let p=[],u=new Set,h=(s,c)=>{let d=Ce(t,c),f=`${s}:${c.getStart(t)}`;u.has(f)||(u.add(f),p.push({name:s,line:d,node:c}))},g=s=>{let c=s.parent&&(e.isPropertyAccessExpression(s.parent)||e.isElementAccessExpression(s.parent))&&s.parent.expression===s;if((e.isPropertyAccessExpression(s)||e.isElementAccessExpression(s))&&!c){let d=l(s),f=d?be(r,d):void 0;f&&h(f,s)}else e.isIdentifier(s)&&r.has(s.text)&&Ue(e,s)&&!ne(e,i,t,s)&&h(s.text,s);if(e.isVariableDeclaration(s)&&e.isObjectBindingPattern(s.name)&&s.initializer){let d=l(s.initializer);if(d)for(let f of s.name.elements){if(!e.isIdentifier(f.name))continue;let y=f.propertyName?_(e,f.propertyName)??f.propertyName.text:f.name.text,m=be(r,[...d,y]);m&&h(m,s.initializer)}}e.forEachChild(s,g)};return g(t),p}var ae={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function ke(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Be(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&ke(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:ae.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:ae.PUBLISH_MISSING_SOURCE}),t}var ze=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),oe=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),ce=Object.freeze(Object.keys(oe).sort()),se=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function j(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=se[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=se[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:se[e.slice(0,a)]??null}function G(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=oe[r];if(i)return i}return null}function qe(e){let t=new Set,n=G(e);n&&t.add(n);for(let[r,i]of Object.entries(oe))(r===e||r.startsWith(`${e}.`))&&t.add(i);return[...t].sort()}function Ke(e,t){let n=[];for(let r of re(e,t)){if(r.typeOnly||!r.specifier)continue;let i=j(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of ie(e,t,ce)){let i=G(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var pe="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",Se=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Ye=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function We(){let e=[];for(let t of Se)for(let n of Se)t===n||Ye.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var Ee=We();var $={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},xe={$schema:"https://json-schema.org/draft/2020-12/schema",$id:pe,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:pe,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...$,minItems:1,default:["src"]},exclude:{...$,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:Ee,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...$,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...$,minItems:1},exclude:$,intentPrefixes:$,description:{type:"string",minLength:1},forbiddenGlobals:$,mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...$,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},M=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
5
+ `)}`),this.name="ArchitectureChangeMapValidationError",this.source=t,this.issues=n}};function te(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function ne(e,t,n,r){for(let i of Object.keys(e))t.includes(i)||r.push({path:`${n}.${i}`,message:"unknown field"})}function P(e,t,n,r){let i=e[t];if(typeof i=="string"&&i.length>0)return i;r.push({path:`${n}.${t}`,message:"must be a non-empty string"})}function Be(e){let t=e.replace(/\\/g,"/");if(!t||t.startsWith("/")||/^[A-Za-z]:\//.test(t)||t.includes("\0"))return;let n=[];for(let i of t.split("/"))if(!(!i||i==="."))if(i===".."){if(n.length===0)return;n.pop()}else n.push(i);let r=n.join("/");return r&&r===t?r:void 0}function ze(e,t,n="architecture change map"){let r=[];if(!te(e))throw new U(n,[{path:"$",message:"must be an object"}]);ne(e,["$schema","schemaVersion","files","dependencies"],"$",r);let i=P(e,"$schema","$",r),a=P(e,"schemaVersion","$",r);a&&a!==ee&&r.push({path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(a)}; expected ${ee}`});let o=new Set(t.layers.map(d=>d.name)),l=[],p=new Set;!Array.isArray(e.files)||e.files.length===0?r.push({path:"$.files",message:"must be a non-empty array"}):e.files.forEach((d,f)=>{let y=`$.files[${f}]`;if(!te(d)){r.push({path:y,message:"must be an object"});return}ne(d,["path","operation","layer"],y,r);let h=P(d,"path",y,r),A=P(d,"operation",y,r),C=P(d,"layer",y,r),k=h?Be(h):void 0;h&&!k&&r.push({path:`${y}.path`,message:"must be a canonical project-relative path"}),A&&!["create","update","delete"].includes(A)&&r.push({path:`${y}.operation`,message:"must be create, update, or delete"}),C&&!o.has(C)&&r.push({path:`${y}.layer`,message:`references unknown layer ${JSON.stringify(C)}`}),k&&p.has(k)&&r.push({path:`${y}.path`,message:`duplicates planned path ${k}`}),k&&p.add(k);let Z=k?_(k,t.layers):void 0;k&&C&&o.has(C)&&Z!==C&&r.push({path:`${y}.layer`,message:Z?`${k} resolves to ${Z}, not ${C}`:`${k} is not assigned to an architecture layer`}),k&&A&&["create","update","delete"].includes(A)&&C&&l.push({path:k,operation:A,layer:C})});let u=[],m=new Map(l.map(d=>[d.path,d.operation])),g=new Set,c=e.dependencies??[];if(Array.isArray(c)?c.forEach((d,f)=>{let y=`$.dependencies[${f}]`;if(!te(d)){r.push({path:y,message:"must be an object"});return}ne(d,["from","to"],y,r);let h=P(d,"from",y,r),A=P(d,"to",y,r);h&&!p.has(h)&&r.push({path:`${y}.from`,message:`must reference a planned file path: ${h}`}),A&&!p.has(A)&&r.push({path:`${y}.to`,message:`must reference a planned file path: ${A}`}),h&&m.get(h)==="delete"&&r.push({path:`${y}.from`,message:`cannot depend from deleted file ${h}`}),A&&m.get(A)==="delete"&&r.push({path:`${y}.to`,message:`cannot depend on deleted file ${A}`}),h&&A&&h===A&&r.push({path:y,message:"must not declare a self dependency"});let C=h&&A?`${h}\0${A}`:void 0;C&&g.has(C)&&r.push({path:y,message:`duplicates dependency ${h} -> ${A}`}),C&&g.add(C),h&&A&&u.push({from:h,to:A})}):r.push({path:"$.dependencies",message:"must be an array"}),r.length>0)throw new U(n,r);let s={$schema:i,schemaVersion:ee,files:l.sort((d,f)=>d.path.localeCompare(f.path)),dependencies:u.sort((d,f)=>d.from.localeCompare(f.from)||d.to.localeCompare(f.to))};return{map:s,hash:$(x(s))}}function w(e){return`${e.from}->${e.to}`}function F(e,t,n,r="dependency"){return{id:`${e}:${r}:${w(t)}`,classification:e,subject:"dependency",from:t.from,to:t.to,message:n,...e==="missing"?{nextAction:`Add the planned dependency ${w(t)} to the candidate, then preflight again.`}:e==="contradictory"?{nextAction:`Replace the reverse dependency with ${w(t)}, then preflight again.`}:e==="unplanned"?{nextAction:`Remove the unplanned dependency ${w(t)} from the candidate, then preflight again.`}:{}}}function re(e){let t=[],n=new Map(e.changeMap.map.files.map(c=>[c.path,c])),r=new Map(e.changes.map(c=>[c.path,c])),i=new Map(e.changeMap.map.dependencies.map(c=>[w(c),c])),a=new Map(e.baseDependencies.map(c=>[w(c),c])),o=new Map(e.candidateDependencies.map(c=>[w(c),c]));for(let c of[...n.values()].sort((s,d)=>s.path.localeCompare(d.path))){let s=r.get(c.path);s?s.operation!==c.operation?t.push({id:`contradictory:file:${c.path}`,classification:"contradictory",subject:"file",path:c.path,expectedOperation:c.operation,actualOperation:s.operation,message:`${c.path} was planned as ${c.operation} but the actual operation is ${s.operation}.`,nextAction:`Change ${c.path} to the planned ${c.operation} operation, then preflight again.`}):t.push({id:`satisfied:file:${c.path}`,classification:"satisfied",subject:"file",path:c.path,expectedOperation:c.operation,actualOperation:s.operation,message:`${c.path} matches the planned ${c.operation} operation.`}):t.push({id:`missing:file:${c.path}`,classification:"missing",subject:"file",path:c.path,expectedOperation:c.operation,message:`${c.path} was planned as ${c.operation} but is absent from the actual change.`,nextAction:`${c.operation[0].toUpperCase()}${c.operation.slice(1)} ${c.path} in the complete change set, then preflight again.`})}for(let c of[...r.values()].sort((s,d)=>s.path.localeCompare(d.path)))n.has(c.path)||t.push({id:`unplanned:file:${c.path}`,classification:"unplanned",subject:"file",path:c.path,actualOperation:c.operation,message:`${c.path} has an unplanned ${c.operation} operation.`,nextAction:`Remove ${c.path} from the change set, then preflight again.`});let l=new Set;for(let c of[...i.values()].sort((s,d)=>w(s).localeCompare(w(d)))){if(o.has(w(c))){t.push(F("satisfied",c,`${c.from} -> ${c.to} exists in the candidate architecture.`));continue}let s={from:c.to,to:c.from};o.has(w(s))?(l.add(w(s)),t.push(F("contradictory",c,`${c.from} -> ${c.to} was planned, but the candidate contains the reverse edge.`))):t.push(F("missing",c,`${c.from} -> ${c.to} is absent from the candidate architecture.`))}let p=new Set([...n.keys(),...r.keys()]);for(let[c,s]of[...o].sort(([d],[f])=>d.localeCompare(f)))a.has(c)||i.has(c)||l.has(c)||!p.has(s.from)&&!p.has(s.to)||t.push({...F("unplanned",s,`${s.from} -> ${s.to} was added without a matching planned dependency.`,"dependency-added"),actualOperation:"added"});let u=new Set(e.changeMap.map.files.filter(c=>c.operation==="delete").map(c=>c.path));for(let[c,s]of[...a].sort(([d],[f])=>d.localeCompare(f)))o.has(c)||u.has(s.from)||u.has(s.to)||!p.has(s.from)&&!p.has(s.to)||t.push({...F("unplanned",s,`${s.from} -> ${s.to} was removed without a planned file deletion.`,"dependency-removed"),actualOperation:"removed"});let m={satisfied:0,missing:1,contradictory:2,unplanned:3};t.sort((c,s)=>m[c.classification]-m[s.classification]||(c.subject===s.subject?0:c.subject==="file"?-1:1)||c.id.localeCompare(s.id));let g={satisfied:t.filter(c=>c.classification==="satisfied").length,missing:t.filter(c=>c.classification==="missing").length,contradictory:t.filter(c=>c.classification==="contradictory").length,unplanned:t.filter(c=>c.classification==="unplanned").length};return{schemaVersion:"1.0",readOnly:!0,changeMapHash:e.changeMap.hash,structurallyConverged:g.missing===0&&g.contradictory===0&&g.unplanned===0,behavioralCompletion:"not-evaluated",summary:g,findings:t}}function M(e,t){return t&&e.isStringLiteralLike(t)?t.text:void 0}function we(e,t){return e.getLineAndCharacterOfPosition(t.getStart(e)).line+1}function Ie(e,t){if(e.isImportDeclaration(t)){let n=t.importClause;if(!n)return!1;if(n.isTypeOnly)return!0;let r=n.namedBindings;return!!(r&&e.isNamedImports(r)&&r.elements.length>0&&r.elements.every(i=>i.isTypeOnly))}if(e.isExportDeclaration(t)){if(t.isTypeOnly)return!0;let n=t.exportClause;return!!(n&&e.isNamedExports(n)&&n.elements.length>0&&n.elements.every(r=>r.isTypeOnly))}return!1}function Se(e,t){let n={noLib:!0,noResolve:!0,target:e.ScriptTarget.Latest},r=e.createCompilerHost(n,!0);return r.getSourceFile=i=>i===t.fileName?t:void 0,r.fileExists=i=>i===t.fileName,r.readFile=i=>i===t.fileName?t.text:void 0,e.createProgram([t.fileName],n,r).getTypeChecker()}function ie(e,t){try{return e.getSymbolAtLocation(t)}catch{return}}function ae(e,t,n,r){let i=r.parent&&e.isShorthandPropertyAssignment(r.parent)&&r.parent.name===r,a;try{a=i?t.getShorthandAssignmentValueSymbol(r.parent):ie(t,r)}catch{a=void 0}return!!a?.declarations?.some(o=>o.getSourceFile().fileName===n.fileName)}function se(e,t){let n,r=[],i=(o,l,p,u=!1)=>r.push({specifier:p,kind:l,line:we(t,o),typeOnly:u,unresolved:p===void 0,node:o}),a=o=>{if(e.isImportDeclaration(o))i(o,"import",M(e,o.moduleSpecifier),Ie(e,o));else if(e.isExportDeclaration(o)&&o.moduleSpecifier)i(o,"export",M(e,o.moduleSpecifier),Ie(e,o));else if(e.isImportEqualsDeclaration(o)&&e.isExternalModuleReference(o.moduleReference))i(o,"require",M(e,o.moduleReference.expression));else if(e.isCallExpression(o)){let l=o.expression.kind===e.SyntaxKind.ImportKeyword,u=e.isIdentifier(o.expression)&&o.expression.text==="require"&&!ae(e,n??(n=Se(e,t)),t,o.expression);(l||u)&&i(o,u?"require":"dynamic-import",M(e,o.arguments[0]))}e.forEachChild(o,a)};return a(t),r}function qe(e,t){let n=[],r=t;for(;e.isPropertyAccessExpression(r)||e.isElementAccessExpression(r);){if(e.isPropertyAccessExpression(r))n.unshift(r.name.text);else{let i=M(e,r.argumentExpression);if(i===void 0)return;n.unshift(i)}r=r.expression}if(e.isIdentifier(r))return n.unshift(r.text),{root:r,segments:n}}function Ke(e,t){let n=t.parent;return e.isPropertyAccessExpression(n)||e.isElementAccessExpression(n)?!1:e.isExpressionNode(t)&&!e.isInTypeQuery(t)||e.isShorthandPropertyAssignment(n)&&n.name===t}function ke(e,t){let n=t[0]==="globalThis"?t.slice(1):t;for(let r=n.length;r>=1;r-=1){let i=n.slice(0,r).join(".");if(e.has(i))return i}}function oe(e,t,n){if(n.length===0)return[];let r=new Set(n),i=Se(e,t),a=new Map,o=new Set;for(let c of t.statements)if(e.isVariableStatement(c))for(let s of c.declarationList.declarations)e.isIdentifier(s.name)&&o.add(s.name.text);let l=c=>{let s=qe(e,c);if(!s)return;let d=ie(i,s.root),f=d?a.get(d):void 0;return f?[...f,...s.segments.slice(1)]:ae(e,i,t,s.root)||o.has(s.root.text)?void 0:s.segments};for(let c of t.statements)if(e.isVariableStatement(c))for(let s of c.declarationList.declarations){if(!s.initializer||!e.isIdentifier(s.name))continue;let d=l(s.initializer),f=ie(i,s.name);!d||!f||a.set(f,d)}let p=[],u=new Set,m=(c,s)=>{let d=we(t,s),f=`${c}:${s.getStart(t)}`;u.has(f)||(u.add(f),p.push({name:c,line:d,node:s}))},g=c=>{let s=c.parent&&(e.isPropertyAccessExpression(c.parent)||e.isElementAccessExpression(c.parent))&&c.parent.expression===c;if((e.isPropertyAccessExpression(c)||e.isElementAccessExpression(c))&&!s){let d=l(c),f=d?ke(r,d):void 0;f&&m(f,c)}else e.isIdentifier(c)&&r.has(c.text)&&Ke(e,c)&&!ae(e,i,t,c)&&m(c.text,c);if(e.isVariableDeclaration(c)&&e.isObjectBindingPattern(c.name)&&c.initializer){let d=l(c.initializer);if(d)for(let f of c.name.elements){if(!e.isIdentifier(f.name))continue;let y=f.propertyName?M(e,f.propertyName)??f.propertyName.text:f.name.text,h=ke(r,[...d,y]);h&&m(h,c.initializer)}}e.forEachChild(c,g)};return g(t),p}var ce={RAW_EVENT_PUBLISH:"Publish through a registered intent creator; raw event objects or intent strings bypass Ark contracts and tooling.",PUBLISH_MISSING_SOURCE:"Strict Ark publish calls must include metadata.source."};function xe(e){return/^(Domain|Application|Adapter|Workflow|Job|Presentation|Reporting|Metadata|Security|Audit|Observability|Kernel)\.[A-Za-z0-9_.]+$/.test(e)}function Ye(e){if(!e.publishCall)return[];let t=[];return(e.rawIntentName!==void 0&&xe(e.rawIntentName)||e.objectHasIntent)&&t.push({ruleId:"RAW_EVENT_PUBLISH",message:ce.RAW_EVENT_PUBLISH}),e.arkPublishCandidate&&!e.hasSource&&t.push({ruleId:"PUBLISH_MISSING_SOURCE",message:ce.PUBLISH_MISSING_SOURCE}),t}var pe=Object.freeze(["network","filesystem","clock","randomness","environment","process","persistence"]),B=Object.freeze({fetch:"network",XMLHttpRequest:"network",Date:"clock","Date.now":"clock","Math.random":"randomness","process.env":"environment",process:"process"}),de=Object.freeze(Object.keys(B).sort()),le=Object.freeze({fs:"filesystem","node:fs":"filesystem","fs/promises":"filesystem","node:fs/promises":"filesystem","fs-extra":"filesystem","graceful-fs":"filesystem",memfs:"filesystem",chokidar:"filesystem",http:"network",https:"network",http2:"network",net:"network",tls:"network",dgram:"network",dns:"network","node:http":"network","node:https":"network","node:http2":"network","node:net":"network","node:tls":"network","node:dgram":"network","node:dns":"network",axios:"network",undici:"network","node-fetch":"network",got:"network",ky:"network",superagent:"network",ws:"network",process:"process","node:process":"process",child_process:"process","node:child_process":"process","@prisma/client":"persistence",prisma:"persistence",pg:"persistence",mysql:"persistence",mysql2:"persistence",mongodb:"persistence",mongoose:"persistence",sqlite3:"persistence","better-sqlite3":"persistence",redis:"persistence",ioredis:"persistence",typeorm:"persistence",knex:"persistence","drizzle-orm":"persistence",sequelize:"persistence",kysely:"persistence","@supabase/supabase-js":"persistence"});function j(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=le[e];if(t)return t;let n=e.indexOf("/");if(n<0)return null;let r=e.slice(0,n),i=le[r];if(i)return i;let a=e.indexOf("/",n+1);return a<0?null:le[e.slice(0,a)]??null}function V(e){let t=e.split(".");for(let n=t.length;n>=1;n-=1){let r=t.slice(0,n).join("."),i=B[r];if(i)return i}return null}function z(e){if(e?.pure===!0)return[...pe].sort();let n=(e?.capabilities?.deny??[]).filter(r=>pe.includes(r));return[...new Set(n)].sort()}function We(e,t){if(t.length===0)return!1;let n=new Set(t),r=e.split(".");for(let i=r.length;i>=1;i-=1)if(n.has(r.slice(0,i).join(".")))return!0;return!1}function q(e){let t=new Set,n=new Set,r=Object.keys(B);for(let i of e?.forbiddenGlobals??[]){let a=r.filter(o=>o===i||o.startsWith(`${i}.`));if(a.length===0)n.add(i);else for(let o of a)t.add(`ambient:${o}`)}for(let i of z(e)){t.add(`import:${i}`);for(let a of r)V(a)===i&&t.add(`ambient:${a}`)}return{atoms:[...t].sort(),rawGlobals:[...n].sort()}}function Je(e){let t=new Set,n=V(e);n&&t.add(n);for(let[r,i]of Object.entries(B))(r===e||r.startsWith(`${e}.`))&&t.add(i);return[...t].sort()}function Ze(e,t){let n=[];for(let r of se(e,t)){if(r.typeOnly||!r.specifier)continue;let i=j(r.specifier);i&&n.push({capability:i,symbol:r.specifier,line:r.line,source:"import-based"})}for(let r of oe(e,t,de)){let i=V(r.name);i&&n.push({capability:i,symbol:r.name,line:r.line,source:"ambient-global"})}return n.sort((r,i)=>r.line-i.line||r.capability.localeCompare(i.capability)||r.symbol.localeCompare(i.symbol))}var ue="https://unpkg.com/arkgate@2/schemas/ark.config.schema.json",Ee=["DomainModel","ApplicationOrchestration","PersistenceAdapters","IntegrationAdapters","WorkflowSagaEngine","BackgroundJobsScheduling","PresentationAdapters","ReportingReadModels","ExtensibilityMetadata","SecurityAuditObservability","Kernel"],Xe=new Set(["PresentationAdapters->ApplicationOrchestration","ApplicationOrchestration->DomainModel","WorkflowSagaEngine->ApplicationOrchestration","WorkflowSagaEngine->DomainModel","BackgroundJobsScheduling->ApplicationOrchestration"]);function Qe(){let e=[];for(let t of Ee)for(let n of Ee)t===n||Xe.has(`${t}->${n}`)||e.push({from:t,to:n,allowed:!1});return e}var Oe=Qe();var O={type:"array",items:{type:"string",minLength:1},uniqueItems:!0},$e={$schema:"https://json-schema.org/draft/2020-12/schema",$id:ue,title:"ArkGate architecture contract",description:"Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",type:"object",additionalProperties:!1,required:["$schema","schemaVersion","include","layers","rules"],properties:{$schema:{type:"string",minLength:1,default:ue,description:"Editor-facing URL or local path for this JSON Schema."},schemaVersion:{type:"string",const:"1.0",default:"1.0"},name:{type:"string",minLength:1},include:{...O,minItems:1,default:["src"]},exclude:{...O,default:[]},excludeGenerated:{type:"boolean",default:!0},frameworkOverlay:{type:"string",minLength:1},layers:{type:"array",default:[],items:{$ref:"#/$defs/layer"}},rules:{type:"array",default:Oe,items:{$ref:"#/$defs/rule"}},cyclePolicy:{type:"string",enum:["strict","soft","framework-soft","off"],default:"strict"},dynamicImportAllowlist:{...O,default:[]},safety:{$ref:"#/$defs/safety",default:{maxTsSuppressions:0,maxAnyCasts:0,allowInMemory:!1,allowDisabledPeerIsolation:!1}}},$defs:{layer:{type:"object",additionalProperties:!1,required:["name","patterns"],properties:{name:{type:"string",minLength:1},patterns:{...O,minItems:1},exclude:O,intentPrefixes:O,description:{type:"string",minLength:1},forbiddenGlobals:O,capabilities:{type:"object",additionalProperties:!1,properties:{deny:{type:"array",uniqueItems:!0,items:{type:"string",enum:["network","filesystem","clock","randomness","environment","process","persistence"]}}}},pure:{type:"boolean"},mayImportInfrastructure:{type:"boolean"},optional:{type:"boolean"}}},rule:{type:"object",additionalProperties:!1,required:["from","to","allowed"],properties:{from:{type:"string",minLength:1},to:{type:"string",minLength:1},allowed:{type:"boolean"},message:{type:"string",minLength:1},peerIsolation:{type:"boolean"},sliceFolders:{...O,minItems:1}}},safety:{type:"object",additionalProperties:!1,properties:{maxTsSuppressions:{type:"integer",minimum:0,default:0},maxAnyCasts:{type:"integer",minimum:0,default:0},allowInMemory:{type:"boolean",default:!1},allowDisabledPeerIsolation:{type:"boolean",default:!1}}}}},T=class extends Error{issues;source;constructor(t,n){super(`Invalid ArkGate config (${t}):
6
6
  ${n.map(r=>`- ${r.path}: ${r.message}`).join(`
7
- `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function we(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function le(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function v(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Je(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function U(e,t,n,r,i){if(t.$ref){let a=Je(t.$ref,r);if(!a){i.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}U(e,a,n,r,i);return}if(t.const!==void 0&&!Object.is(e,t.const)){i.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(a=>Object.is(a,e))){i.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!we(e)){i.push({path:n,message:`must be an object; received ${v(e)}`});return}let a=t.properties??{};for(let o of t.required??[])e[o]===void 0&&i.push({path:le(n,o),message:"is required"});if(t.additionalProperties===!1)for(let o of Object.keys(e))o in a||i.push({path:le(n,o),message:"unknown field"});for(let[o,l]of Object.entries(a))e[o]!==void 0&&U(e[o],l,le(n,o),r,i);return}if(t.type==="array"){if(!Array.isArray(e)){i.push({path:n,message:`must be an array; received ${v(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&i.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let a=e.map(o=>JSON.stringify(o));new Set(a).size!==a.length&&i.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((a,o)=>U(a,t.items,`${n}[${o}]`,r,i));return}if(t.type==="string"){if(typeof e!="string"){i.push({path:n,message:`must be a string; received ${v(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&i.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&i.push({path:n,message:`must be a boolean; received ${v(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){i.push({path:n,message:`must be an integer; received ${v(e)}`});return}t.minimum!==void 0&&e<t.minimum&&i.push({path:n,message:`must be at least ${t.minimum}`})}}function Ze(e){return{...e,$schema:e.$schema===void 0?pe:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?Ee.map(t=>({...t})):e.rules}}function Xe(e,t="ark.config.json"){if(!we(e))throw new M(t,[{path:"$",message:`must be an object; received ${v(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new M(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:Ze(e),migratedFrom:n}}function de(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=Xe(e,t),i=[];if(U(n,xe,"$",xe,i),i.length>0)throw new M(t,i);return{config:n,migratedFrom:r}}function $e(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new M(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return de(n,t)}function b(e,t){e.push({id:`${t.classification}:${t.path}:${t.kind}`,path:t.path,classification:t.classification,message:t.message,...t.classification==="weakening"||t.classification==="judgment-required"?{nextAction:`Restore the previous protection at ${t.path}, then run ArkGate again.`}:{},...t.before===void 0?{}:{before:t.before},...t.after===void 0?{}:{after:t.after}})}function x(e){return[...new Set(e??[])].sort()}function N(e,t,n,r,i){let a=x(n),o=x(r),l=new Set(a),p=new Set(o),u=o.filter(g=>!l.has(g)),h=a.filter(g=>!p.has(g));u.length===0&&h.length===0||(u.length>0&&b(e,{kind:"added",path:t,classification:i.added,message:i.addedMessage,before:a,after:o}),h.length>0&&b(e,{kind:"removed",path:t,classification:i.removed,message:i.removedMessage,before:a,after:o}))}function B(e,t,n,r,i,a,o){if(n===r)return;b(e,{kind:r?"enabled":"disabled",path:t,classification:r?i:i==="strengthening"?"weakening":"strengthening",message:r?a:o,before:n,after:r})}function z(e,t){let n=new Map,r=new Set;for(let i of e){let a=t(i);n.has(a)?r.add(a):n.set(a,i)}return{values:n,duplicates:[...r].sort()}}function Qe(e,t,n){let r=z(t,a=>a.name),i=z(n,a=>a.name);(r.duplicates.length>0||i.duplicates.length>0)&&b(e,{kind:"duplicate-layer",path:"$.layers",classification:"judgment-required",message:"Duplicate layer names make policy ownership ambiguous.",before:r.duplicates,after:i.duplicates});for(let a of[...new Set([...r.values.keys(),...i.values.keys()])].sort()){let o=r.values.get(a),l=i.values.get(a),p=`$.layers[${a}]`;if(!o&&l){b(e,{kind:"layer-added",path:p,classification:"judgment-required",message:"A layer was added; verify overlap, ownership, and rule coverage.",after:l});continue}if(o&&!l){b(e,{kind:"layer-removed",path:p,classification:"weakening",message:"Removing a layer can leave its source paths ungoverned.",before:o});continue}!o||!l||(N(e,`${p}.patterns`,o.patterns,l.patterns,{added:"strengthening",removed:"weakening",addedMessage:"Additional paths are governed by this layer.",removedMessage:"Paths were removed from this layer and may become ungoverned."}),N(e,`${p}.exclude`,o.exclude,l.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional paths are excluded from this layer.",removedMessage:"Fewer paths are excluded from this layer."}),N(e,`${p}.forbiddenGlobals`,o.forbiddenGlobals,l.forbiddenGlobals,{added:"strengthening",removed:"weakening",addedMessage:"Additional forbidden globals are enforced in this layer.",removedMessage:"A forbidden-global protection was removed from this layer."}),x(o.intentPrefixes).join("\0")!==x(l.intentPrefixes).join("\0")&&b(e,{kind:"intent-prefixes-changed",path:`${p}.intentPrefixes`,classification:"judgment-required",message:"Intent ownership changed and must be reviewed against publishers and consumers.",before:x(o.intentPrefixes),after:x(l.intentPrefixes)}),B(e,`${p}.mayImportInfrastructure`,o.mayImportInfrastructure===!0,l.mayImportInfrastructure===!0,"weakening","The layer may now import infrastructure directly.","Direct infrastructure imports are no longer allowed for this layer."),B(e,`${p}.optional`,o.optional===!0,l.optional===!0,"weakening","The layer is now optional and can be absent without a strict warning.","The layer is now required when its contract is active."))}}function et(e,t,n){let r=o=>`${o.from}->${o.to}`,i=z(t,r),a=z(n,r);(i.duplicates.length>0||a.duplicates.length>0)&&b(e,{kind:"duplicate-rule",path:"$.rules",classification:"judgment-required",message:"Duplicate rule edges make the effective verdict order-dependent.",before:i.duplicates,after:a.duplicates});for(let o of[...new Set([...i.values.keys(),...a.values.keys()])].sort()){let l=i.values.get(o),p=a.values.get(o),u=`$.rules[${o}]`;if(!l&&p){p.allowed===!1&&b(e,{kind:"deny-added",path:u,classification:"strengthening",message:"A denied dependency edge was added.",after:p});continue}if(l&&!p){l.allowed===!1&&b(e,{kind:"deny-removed",path:u,classification:"weakening",message:"A denied dependency edge was removed.",before:l});continue}if(!l||!p)continue;l.allowed!==p.allowed&&b(e,{kind:p.allowed?"deny-disabled":"deny-enabled",path:`${u}.allowed`,classification:p.allowed?"weakening":"strengthening",message:p.allowed?"A previously denied dependency edge is now allowed.":"A dependency edge is now denied.",before:l.allowed,after:p.allowed});let h=l.peerIsolation===!0,g=p.peerIsolation===!0;if(h!==g){let s=l.from===l.to&&p.from===p.to;b(e,{kind:g?"peer-isolation-enabled":"peer-isolation-disabled",path:`${u}.peerIsolation`,classification:s?g?"strengthening":"weakening":"judgment-required",message:s?g?"Cross-slice dependencies inside this layer are now denied.":"Cross-slice dependencies inside this layer are no longer denied.":"Changing peer isolation on a cross-layer edge changes the denial scope.",before:h,after:g})}x(l.sliceFolders).join("\0")!==x(p.sliceFolders).join("\0")&&b(e,{kind:"slice-folders-changed",path:`${u}.sliceFolders`,classification:"judgment-required",message:"Slice ownership folders changed and can reclassify existing dependencies.",before:x(l.sliceFolders),after:x(p.sliceFolders)})}}function tt(e,t,n){let r=t.safety??{},i=n.safety??{};for(let a of["maxTsSuppressions","maxAnyCasts"]){let o=r[a]??0,l=i[a]??0;o!==l&&b(e,{kind:l>o?"threshold-raised":"threshold-lowered",path:`$.safety.${a}`,classification:l>o?"weakening":"strengthening",message:l>o?"The safety threshold allows more violations.":"The safety threshold allows fewer violations.",before:o,after:l})}for(let a of["allowInMemory","allowDisabledPeerIsolation"])B(e,`$.safety.${a}`,r[a]===!0,i[a]===!0,"weakening","A safety exception was enabled.","A safety exception was disabled.")}function nt(e){return e.some(t=>t.classification==="weakening")?"weakening":e.some(t=>t.classification==="judgment-required")?"judgment-required":e.some(t=>t.classification==="strengthening")?"strengthening":"neutral"}function Re(e,t){let n=[];N(n,"$.include",e.include,t.include,{added:"strengthening",removed:"weakening",addedMessage:"Additional project roots are governed.",removedMessage:"Project roots were removed from governance."}),N(n,"$.exclude",e.exclude,t.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional project paths are excluded from governance.",removedMessage:"Fewer project paths are excluded from governance."}),N(n,"$.dynamicImportAllowlist",e.dynamicImportAllowlist,t.dynamicImportAllowlist,{added:"weakening",removed:"strengthening",addedMessage:"Additional files may use non-literal dynamic imports.",removedMessage:"Fewer files may use non-literal dynamic imports."}),B(n,"$.excludeGenerated",e.excludeGenerated!==!1,t.excludeGenerated!==!1,"weakening","Generated source is now excluded from governance.","Generated source is now governed.");let r={off:0,soft:1,"framework-soft":1,strict:2},i=e.cyclePolicy??"strict",a=t.cyclePolicy??"strict";if(i!==a){let o=r[a]===r[i]?"judgment-required":r[a]>r[i]?"strengthening":"weakening";b(n,{kind:"cycle-policy-changed",path:"$.cyclePolicy",classification:o,message:"The cycle enforcement level changed.",before:i,after:a})}return(e.frameworkOverlay??null)!==(t.frameworkOverlay??null)&&b(n,{kind:"framework-overlay-changed",path:"$.frameworkOverlay",classification:"judgment-required",message:"The framework overlay changed and may alter effective layer matching.",before:e.frameworkOverlay??null,after:t.frameworkOverlay??null}),Qe(n,e.layers,t.layers),et(n,e.rules,t.rules),tt(n,e,t),n.sort((o,l)=>o.path.localeCompare(l.path)||o.id.localeCompare(l.id)),{schemaVersion:"1.0",classification:nt(n),findings:n}}function Oe(e,t){if(!e||e.schemaVersion!=="1.0"||typeof e.basePolicyHash!="string"||typeof e.candidatePolicyHash!="string"||typeof e.reason!="string"||!Array.isArray(e.findingIds)||e.findingIds.some(i=>typeof i!="string")||e.reason.trim().length===0||e.basePolicyHash!==t.basePolicyHash||e.candidatePolicyHash!==t.candidatePolicyHash)return!1;let n=x(e.findingIds),r=x(t.findingIds);return n.length===r.length&&n.every((i,a)=>i===r[a])}function P(e){let t=[];for(let n of e.replace(/\\/g,"/").split("/"))!n||n==="."||(n===".."&&t.length>0&&t.at(-1)!==".."?t.pop():t.push(n));return t.join("/")}function Pe(e){return e!==void 0&&/[A-Za-z0-9_$]/.test(e)}function T(e,t){for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t}function q(e,t){let n=e[t];if(n!=="'"&&n!=='"')return;let r=t,i="";for(t+=1;t<e.length;t+=1){let a=e[t];if(a===n)return{value:i,offset:r,excerpt:e.slice(r,t+1)};a==="\\"&&t+1<e.length?(i+=e[t+1],t+=1):i+=a}}function R(e,t,n){return e.startsWith(t,n)&&!Pe(e[n-1])&&!Pe(e[n+t.length])}function rt(e,t){if(t=T(e,t+6),e[t]==="(")return q(e,T(e,t+1));let n=!1;if(R(e,"type",t)){let i=T(e,t+4);e[i]!==","&&!R(e,"from",i)&&(n=!0)}let r=Le(e,t,!0);return r&&n?{...r,typeOnly:!0}:r}function it(e,t){t=t+6;let n=T(e,t),r=!1;if(R(e,"type",n)){let a=T(e,n+4);(e[a]==="{"||e[a]==="*")&&(r=!0)}let i=Le(e,t,!1);return i&&r?{...i,typeOnly:!0}:i}function Le(e,t,n){for(;t<e.length;t+=1){if(e[t]===";")return;if(R(e,"from",t))return q(e,T(e,t+4));if(n&&(e[t]==="'"||e[t]==='"'))return q(e,t);if(t>0&&(R(e,"import",t)||R(e,"export",t)))return}}function at(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="/"&&e[n+1]==="/"){if(n=e.indexOf(`
8
- `,n+2),n<0)break;continue}if(r==="/"&&e[n+1]==="*"){let a=e.indexOf("*/",n+2);if(a<0)break;n=a+1;continue}if(r==="'"||r==='"'||r==="`"){let a=q(e,n);a&&(n=a.offset+a.excerpt.length-1);continue}let i=R(e,"import",n)?rt(e,n):R(e,"export",n)?it(e,n):void 0;i&&t.push(i)}return t}function _e(e,t){let n=[],r=[];for(let i of at(e.content)){let a=i.value;if(!a.startsWith(".")){if(i.typeOnly)continue;let u=j(a);if(!u)continue;let h=e.content.slice(0,i.offset).split(`
9
- `).length;r.push({file:e.path,symbol:a,capability:u,evidence:{kind:"import",file:e.path,line:h,excerpt:i.excerpt}});continue}let o=e.content.slice(0,i.offset).split(`
10
- `).length,l={kind:"import",file:e.path,line:o,excerpt:i.excerpt},p=st(e.path,a,t);n.push({from:e.path,specifier:a,to:p?.path??null,resolution:p?"resolved":"unresolved",fromLayer:e.layer,toLayer:p?.layer??null,evidence:l})}return{edges:n,capabilityUses:r}}function st(e,t,n){let r=e.split("/");r.pop();for(let a of t.split("/"))a==="."||a===""||(a===".."?r.pop():r.push(a));let i=r.join("/");for(let a of[i,`${i}.ts`,`${i}.tsx`,`${i}.mts`,`${i}.cts`,`${i}/index.ts`,`${i}/index.tsx`]){let o=n.get(a);if(o)return o}}function ve(e,t){let n=[];for(let r of e){if(!r.to||!r.fromLayer||!r.toLayer)continue;let i=H(t.rules,r.fromLayer,r.toLayer,{fromPath:r.from,toPath:r.to,layers:t.layers});i&&n.push({ruleId:`layer-dependency:${i.from}->${i.to}`,message:i.message??`${i.from} must not depend on ${i.to}.`,edge:r,evidence:r.evidence})}return n}function fe(e,t){let n=typeof e=="string"?$e(e,t):de(e,t);return{...n,policyHash:w(E(n.config))}}function ct(e){let t=fe(e.baseConfig,e.baseSource??"base ark.config.json"),n=fe(e.candidateConfig,e.candidateSource??"candidate ark.config.json"),r=Re(t.config,n.config),i=r.findings.filter(l=>l.classification==="weakening"||l.classification==="judgment-required").map(l=>l.id).sort(),a=i.length>0,o=a&&Oe(e.acknowledgement,{basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,findingIds:i});return{schemaVersion:r.schemaVersion,basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,classification:r.classification,findings:r.findings,blockingFindingIds:i,requiresAcknowledgement:a,acknowledged:o,valid:!a||o}}function K(e){let t=e.files.map(o=>{let l=P(o.path);return{path:l,content:o.content,contentHash:w(o.content),layer:L(l,e.contract.config.layers)??null}}).sort((o,l)=>o.path.localeCompare(l.path)),n=new Map(t.map(o=>[o.path,o])),r=[],i=[];for(let o of t){let l=_e(o,n);r.push(...l.edges),i.push(...l.capabilityUses)}let a=ve(r,e.contract.config);return{ir:{schemaVersion:"1.0",policyHash:e.contract.policyHash,compilerOptionsHash:w(E(e.compilerOptions??{})),files:t,layers:e.contract.config.layers.map(o=>o.name),edges:r,capabilityUses:i,violations:a}}}function ue(e){let t=new Map(e.files.map(n=>[P(n.path),n]));for(let n of e.changes){let r=P(n.path);"delete"in n&&n.delete?t.delete(r):"content"in n&&t.set(r,{path:r,content:n.content})}return K({contract:e.contract,files:[...t.values()],compilerOptions:e.compilerOptions})}function lt(e){let t=`${e.evidence.file}:${e.evidence.line}`;if(!e.edge)return`${e.ruleId} at ${t}: ${e.message}`;let n=e.edge.to??e.edge.specifier;return`${e.ruleId} at ${t}: ${e.edge.from} imports ${n}. ${e.message}`}function Me(e){let t=0,n=new Map,r=new Map,i=new Set,a=[],o=[],l=p=>{n.set(p,t),r.set(p,t),t+=1,a.push(p),i.add(p);for(let g of[...e.get(p)??[]].sort())e.has(g)&&(n.has(g)?i.has(g)&&r.set(p,Math.min(r.get(p)??0,n.get(g)??0)):(l(g),r.set(p,Math.min(r.get(p)??0,r.get(g)??0))));if(r.get(p)!==n.get(p))return;let u=[],h;do{if(h=a.pop(),h===void 0)break;i.delete(h),u.push(h)}while(h!==p);u.length>1&&o.push(u.sort())};for(let p of[...e.keys()].sort())n.has(p)||l(p);return o.sort((p,u)=>p[0].localeCompare(u[0])).map(p=>({ruleId:"CIRCULAR_DEPENDENCY",file:p[0],line:1,target:p.join(" \u2192 "),message:`Circular dependency among ${p.length} files: ${p.join(" \u2192 ")} \u2192 ${p[0]}.`,cycleKind:"value"}))}function ge(e){let t=e.contentViolations.map(a=>({...a})),n=(e.warnings??[]).map(a=>({...a})),r=new Map(e.files.map(a=>[a,new Set]));for(let a of e.edges){if(a.to&&a.to!==a.from&&!a.typeOnly&&r.has(a.from)&&r.get(a.from)?.add(a.to),!a.to||!a.toLayer)continue;let o=H(e.rules,a.fromLayer,a.toLayer,{fromPath:a.from,toPath:a.to,layers:e.config.layers});if(!o)continue;let l=!!o.peerIsolation;t.push({ruleId:"LAYER_IMPORT_VIOLATION",file:a.from,line:a.line,fromLayer:a.fromLayer,toLayer:a.toLayer,target:a.to,...a.typeOnly?{typeOnly:!0}:{},...a.targetTypeOnlyExports?{targetTypeOnlyExports:!0}:{},...a.sourcePureTypeModule?{sourcePureTypeModule:!0}:{},...a.namedBindingsTypeOnly?{namedBindingsTypeOnly:!0}:{},...!l&&a.portProofEligible?{portProofEligible:!0}:{},...a.kind?{edgeKind:a.kind}:{},...l?{peerIsolation:!0}:{},message:o.message??(l?`${a.fromLayer} must not ${a.kind} another slice of ${a.toLayer} (${a.from} \u2192 ${a.to}). Extract shared code or use events/ports across slices.`:`${a.fromLayer} must not ${a.kind} ${a.toLayer}.`)})}let i=String(e.config.cyclePolicy??"strict").toLowerCase();if(i!=="off"){let a=Me(r);i==="soft"||i==="framework-soft"?n.push(...a.map(o=>({...o,message:`${o.message} (soft cycle policy \u2014 advisory only; set cyclePolicy: "strict" to fail the check)`,failsStrict:!1}))):t.push(...a)}return{violations:t,warnings:n,safety:e.safety}}function Ne(e){switch(e.ruleId){case"LAYER_IMPORT_VIOLATION":return e.typeOnly||e.targetTypeOnlyExports||e.namedBindingsTypeOnly?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":e.peerIsolation?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${e.fromLayer??"the source layer"}, inject the ${e.toLayer??"outer-layer"} implementation, then preflight again.`;case"FORBIDDEN_GLOBAL":return`Inject ${e.target??"the capability"} through a port, then preflight again.`;case"CIRCULAR_DEPENDENCY":return"Extract the shared dependency into a third module, then preflight again.";case"RAW_EVENT_PUBLISH":return"Publish through a registered intent creator, then run Ark again.";case"PUBLISH_MISSING_SOURCE":return"Add metadata.source to the publish call, then run Ark again.";default:return`Resolve ${typeof e.ruleId=="string"&&e.ruleId.length>0?e.ruleId:"ARK_UNKNOWN"} without weakening ark.config.json, then run Ark again.`}}function Te(e){return w(E(e.map(({path:t,contentHash:n})=>({path:t,contentHash:n}))))}function pt(e){let t=K(e),n=new Map(t.ir.files.map(s=>[s.path,s])),r=[],i=new Set,a=[];for(let s of e.changes){let c=s.path.replace(/\\/g,"/"),d=P(s.path);if(!d||d===".."||d.startsWith("../")||c.startsWith("/")||/^[A-Za-z]:\//.test(c)||c.includes("\0")){a.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a safe, non-empty project-relative path."});continue}if(i.has(d)){a.push({ruleId:"DUPLICATE_CHANGE_PATH",file:d,line:1,message:`The atomic change set contains more than one operation for ${d}.`});continue}i.add(d),"delete"in s&&s.delete&&!n.has(d)&&a.push({ruleId:"DELETE_TARGET_MISSING",file:d,line:1,message:`Cannot delete ${d} because it is not present in the supplied base tree.`}),r.push("delete"in s&&s.delete?{path:d,delete:!0}:{path:d,content:"content"in s?s.content:""})}e.changes.length===0&&a.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});let o=ue({...e,changes:r}),l=new Map(o.ir.files.map(s=>[s.path,s])),p=ge({config:e.contract.config,rules:e.contract.config.rules,files:o.ir.files.map(s=>s.path),contentViolations:[],edges:o.ir.edges.filter(s=>!!s.fromLayer).map(s=>({from:s.from,fromLayer:s.fromLayer,...s.to?{to:s.to}:{},...s.toLayer?{toLayer:s.toLayer}:{},line:s.evidence.line,kind:"import"}))}),u=r.map(s=>{let c=P(s.path),d=n.get(c),f=l.get(c);return{path:c,operation:"delete"in s&&s.delete?"delete":d?"update":"create",...d?{beforeContentHash:d.contentHash}:{},...f?{candidateContentHash:f.contentHash}:{}}}).sort((s,c)=>s.path.localeCompare(c.path)),h=[...a,...p.violations].map(s=>({...s,nextAction:Ne(s)})),g=e.changeMap?ee({changeMap:e.changeMap,changes:u,baseDependencies:t.ir.edges.flatMap(s=>s.to?[{from:s.from,to:s.to}]:[]),candidateDependencies:o.ir.edges.flatMap(s=>s.to?[{from:s.from,to:s.to}]:[])}):void 0;return{schemaVersion:"1.0",valid:h.length===0&&(g?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,compilerOptionsHash:o.ir.compilerOptionsHash,baseTreeHash:Te(t.ir.files),candidateTreeHash:Te(o.ir.files),...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...g?{convergence:g}:{},changes:u,violations:h,warnings:p.warnings}}function I(e,t,n={}){return{ruleId:e,message:t,...n}}function dt(e){let{config:t,rules:n,files:r,manifest:i}=e,a=[];if(t.dynamicImportAllowlist!==void 0&&(!Array.isArray(t.dynamicImportAllowlist)||t.dynamicImportAllowlist.some(c=>typeof c!="string"))&&a.push(I("CONFIG_INVALID_DYNAMIC_IMPORT_ALLOWLIST","dynamicImportAllowlist must be an array of file globs.")),t.safety!==void 0&&(t.safety===null||typeof t.safety!="object"||Array.isArray(t.safety)))a.push(I("CONFIG_INVALID_SAFETY","safety must be an object."));else if(t.safety)for(let c of["maxTsSuppressions","maxAnyCasts"]){let d=t.safety[c];d!==void 0&&(!Number.isInteger(d)||d<0)&&a.push(I("CONFIG_INVALID_SAFETY_THRESHOLD",`safety.${c} must be a non-negative integer.`))}let o=Array.isArray(t.layers)?t.layers:[],l=Array.isArray(i?.architecture?.layers)?i.architecture.layers:[],p=new Set([...o.map(c=>c.name).filter(Boolean),...l.map(c=>c.name).filter(c=>!!c)]);o.length===0&&a.push(I("CONFIG_NO_LAYERS","No file layers are configured; ark-check cannot classify files for import-boundary enforcement."));let u=new Set,h=new Set;for(let c of o){if(!c.name){a.push(I("CONFIG_LAYER_WITHOUT_NAME","A configured layer is missing a name."));continue}u.has(c.name)&&h.add(c.name),u.add(c.name),c.forbiddenGlobals!==void 0&&(!Array.isArray(c.forbiddenGlobals)||c.forbiddenGlobals.some(f=>typeof f!="string"))&&a.push(I("CONFIG_INVALID_FORBIDDEN_GLOBALS",`Layer "${c.name}" has an invalid forbiddenGlobals value; expected an array of strings (e.g. ["fetch", "Date.now"]). The entry is ignored.`,{layer:c.name}));let d=Array.isArray(c.patterns)?c.patterns:[];if(d.length===0){a.push(I("CONFIG_LAYER_WITHOUT_PATTERNS",`Layer "${c.name}" has no file patterns and will never classify files.`,{layer:c.name}));continue}for(let f of d){let y;try{y=D(f)}catch(m){a.push(I("CONFIG_INVALID_LAYER_PATTERN",`Layer "${c.name}" has an invalid pattern "${f}": ${m instanceof Error?m.message:String(m)}`,{layer:c.name,pattern:f}));continue}!r.some(m=>y.test(m))&&!c.optional&&a.push(I("CONFIG_LAYER_PATTERN_NO_MATCHES",`Layer "${c.name}" pattern "${f}" matched no included files.`,{layer:c.name,pattern:f,failsStrict:!1}))}}for(let c of h)a.push(I("CONFIG_DUPLICATE_LAYER",`Layer "${c}" is configured more than once.`,{layer:c}));if(p.size>0)for(let c of n??[])c.from&&!p.has(c.from)&&a.push(I("CONFIG_RULE_UNKNOWN_FROM_LAYER",`Rule references unknown source layer "${c.from}".`,{fromLayer:c.from,toLayer:c.to})),c.to&&!p.has(c.to)&&a.push(I("CONFIG_RULE_UNKNOWN_TO_LAYER",`Rule references unknown target layer "${c.to}".`,{fromLayer:c.from,toLayer:c.to}));let g=new Set;if(o.length>1)for(let c of r){let d=-1,f=[];for(let y of o)for(let m of y.patterns??[]){if(!D(m).test(c))continue;let A=J(m);A>d?(d=A,f=[y.name]):A===d&&!f.includes(y.name)&&f.push(y.name)}f.length>1&&g.add([...f].sort().join(" + "))}g.size>0&&a.push(I("CONFIG_AMBIGUOUS_LAYERS",`Some files match multiple layers at equal specificity; classification falls back to declaration order. Disambiguate the overlapping patterns: ${[...g].join(", ")}.`,{pairs:[...g]}));let s=r.filter(c=>!L(c,o));return s.length>0&&a.push(I("CONFIG_UNCLASSIFIED_FILES",`${s.length} included source file(s) are not matched by any configured layer; ark-check will not enforce import rules for those source files.`,{count:s.length,samples:s.slice(0,5)})),a}export{ce as AMBIENT_CAPABILITY_ENTRIES,ze as CAPABILITY_IDS,ae as SOURCE_POLICY_MESSAGES,ee as analyzeArchitectureConvergence,ue as analyzeChange,ct as analyzePolicyDelta,K as analyzeProject,G as capabilityForAmbientName,j as capabilityForModuleSpecifier,Be as classifyPublishFacts,dt as collectAnalysisConfigWarnings,Ke as collectCapabilityUses,ie as collectForbiddenCapabilityUses,Me as detectArchitectureCycles,ge as evaluateArchitectureGraph,lt as explainViolation,re as extractSemanticDependencies,Ve as loadArchitectureChangeMap,fe as loadContract,ke as looksLikeArkIntent,qe as lowerForbiddenGlobal,pt as preflightChange};
7
+ `)}`),this.name="ArkConfigValidationError",this.source=t,this.issues=n}};function Le(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function fe(e,t){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(t)?`${e}.${t}`:`${e}[${JSON.stringify(t)}]`}function N(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function et(e,t){let n="#/$defs/";if(e.startsWith(n))return t.$defs[e.slice(n.length)]}function K(e,t,n,r,i){if(t.$ref){let a=et(t.$ref,r);if(!a){i.push({path:n,message:`schema reference ${t.$ref} cannot be resolved`});return}K(e,a,n,r,i);return}if(t.const!==void 0&&!Object.is(e,t.const)){i.push({path:n,message:`must equal ${JSON.stringify(t.const)}`});return}if(t.enum&&!t.enum.some(a=>Object.is(a,e))){i.push({path:n,message:`must be one of ${t.enum.map(String).join(", ")}`});return}if(t.type==="object"){if(!Le(e)){i.push({path:n,message:`must be an object; received ${N(e)}`});return}let a=t.properties??{};for(let o of t.required??[])e[o]===void 0&&i.push({path:fe(n,o),message:"is required"});if(t.additionalProperties===!1)for(let o of Object.keys(e))o in a||i.push({path:fe(n,o),message:"unknown field"});for(let[o,l]of Object.entries(a))e[o]!==void 0&&K(e[o],l,fe(n,o),r,i);return}if(t.type==="array"){if(!Array.isArray(e)){i.push({path:n,message:`must be an array; received ${N(e)}`});return}if(t.minItems!==void 0&&e.length<t.minItems&&i.push({path:n,message:`must contain at least ${t.minItems} item(s)`}),t.uniqueItems){let a=e.map(o=>JSON.stringify(o));new Set(a).size!==a.length&&i.push({path:n,message:"must not contain duplicate items"})}t.items&&e.forEach((a,o)=>K(a,t.items,`${n}[${o}]`,r,i));return}if(t.type==="string"){if(typeof e!="string"){i.push({path:n,message:`must be a string; received ${N(e)}`});return}t.minLength!==void 0&&e.length<t.minLength&&i.push({path:n,message:`must contain at least ${t.minLength} character(s)`});return}if(t.type==="boolean"){typeof e!="boolean"&&i.push({path:n,message:`must be a boolean; received ${N(e)}`});return}if(t.type==="integer"){if(!Number.isInteger(e)){i.push({path:n,message:`must be an integer; received ${N(e)}`});return}t.minimum!==void 0&&e<t.minimum&&i.push({path:n,message:`must be at least ${t.minimum}`})}}function tt(e){return{...e,$schema:e.$schema===void 0?ue:e.$schema,schemaVersion:e.schemaVersion===void 0?"1.0":e.schemaVersion,include:e.include===void 0?["src"]:e.include,layers:e.layers===void 0?[]:e.layers,rules:e.rules===void 0?Oe.map(t=>({...t})):e.rules}}function nt(e,t="ark.config.json"){if(!Le(e))throw new T(t,[{path:"$",message:`must be an object; received ${N(e)}`}]);let n=e.schemaVersion===void 0?"unversioned":null;if(e.schemaVersion!==void 0&&e.schemaVersion!=="1.0")throw new T(t,[{path:"$.schemaVersion",message:`unsupported version ${JSON.stringify(e.schemaVersion)}; expected 1.0`}]);return{candidate:tt(e),migratedFrom:n}}function ge(e,t="ark.config.json"){let{candidate:n,migratedFrom:r}=nt(e,t),i=[];if(K(n,$e,"$",$e,i),i.length>0)throw new T(t,i);return{config:n,migratedFrom:r}}function Pe(e,t="ark.config.json"){let n;try{n=JSON.parse(e)}catch(r){throw new T(t,[{path:"$",message:`invalid JSON: ${r instanceof Error?r.message:String(r)}`}])}return ge(n,t)}var Re="1.0";function b(e,t){e.push({id:`${t.classification}:${t.path}:${t.kind}`,path:t.path,classification:t.classification,message:t.message,...t.classification==="weakening"||t.classification==="judgment-required"?{nextAction:`Restore the previous protection at ${t.path}, then run ArkGate again.`}:{},...t.before===void 0?{}:{before:t.before},...t.after===void 0?{}:{after:t.after}})}function S(e){return[...new Set(e??[])].sort()}function R(e,t,n,r,i){let a=S(n),o=S(r),l=new Set(a),p=new Set(o),u=o.filter(g=>!l.has(g)),m=a.filter(g=>!p.has(g));u.length===0&&m.length===0||(u.length>0&&b(e,{kind:"added",path:t,classification:i.added,message:i.addedMessage,before:a,after:o}),m.length>0&&b(e,{kind:"removed",path:t,classification:i.removed,message:i.removedMessage,before:a,after:o}))}function Y(e,t,n,r,i,a,o){if(n===r)return;b(e,{kind:r?"enabled":"disabled",path:t,classification:r?i:i==="strengthening"?"weakening":"strengthening",message:r?a:o,before:n,after:r})}function W(e,t){let n=new Map,r=new Set;for(let i of e){let a=t(i);n.has(a)?r.add(a):n.set(a,i)}return{values:n,duplicates:[...r].sort()}}function rt(e,t,n){let r=W(t,a=>a.name),i=W(n,a=>a.name);(r.duplicates.length>0||i.duplicates.length>0)&&b(e,{kind:"duplicate-layer",path:"$.layers",classification:"judgment-required",message:"Duplicate layer names make policy ownership ambiguous.",before:r.duplicates,after:i.duplicates});for(let a of[...new Set([...r.values.keys(),...i.values.keys()])].sort()){let o=r.values.get(a),l=i.values.get(a),p=`$.layers[${a}]`;if(!o&&l){b(e,{kind:"layer-added",path:p,classification:"judgment-required",message:"A layer was added; verify overlap, ownership, and rule coverage.",after:l});continue}if(o&&!l){b(e,{kind:"layer-removed",path:p,classification:"weakening",message:"Removing a layer can leave its source paths ungoverned.",before:o});continue}if(!o||!l)continue;R(e,`${p}.patterns`,o.patterns,l.patterns,{added:"strengthening",removed:"weakening",addedMessage:"Additional paths are governed by this layer.",removedMessage:"Paths were removed from this layer and may become ungoverned."}),R(e,`${p}.exclude`,o.exclude,l.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional paths are excluded from this layer.",removedMessage:"Fewer paths are excluded from this layer."});let u=q(o),m=q(l);R(e,`${p}.forbiddenGlobals`,u.rawGlobals,m.rawGlobals,{added:"strengthening",removed:"weakening",addedMessage:"Additional forbidden globals are enforced in this layer.",removedMessage:"A forbidden-global protection was removed from this layer."}),R(e,`${p}.capabilities`,u.atoms,m.atoms,{added:"strengthening",removed:"weakening",addedMessage:"Additional ambient/import protection is enforced in this layer (coverage atoms).",removedMessage:"An ambient/import protection was lost from this layer (coverage atoms)."}),S(o.intentPrefixes).join("\0")!==S(l.intentPrefixes).join("\0")&&b(e,{kind:"intent-prefixes-changed",path:`${p}.intentPrefixes`,classification:"judgment-required",message:"Intent ownership changed and must be reviewed against publishers and consumers.",before:S(o.intentPrefixes),after:S(l.intentPrefixes)}),Y(e,`${p}.mayImportInfrastructure`,o.mayImportInfrastructure===!0,l.mayImportInfrastructure===!0,"weakening","The layer may now import infrastructure directly.","Direct infrastructure imports are no longer allowed for this layer."),Y(e,`${p}.optional`,o.optional===!0,l.optional===!0,"weakening","The layer is now optional and can be absent without a strict warning.","The layer is now required when its contract is active.")}}function it(e,t,n){let r=o=>`${o.from}->${o.to}`,i=W(t,r),a=W(n,r);(i.duplicates.length>0||a.duplicates.length>0)&&b(e,{kind:"duplicate-rule",path:"$.rules",classification:"judgment-required",message:"Duplicate rule edges make the effective verdict order-dependent.",before:i.duplicates,after:a.duplicates});for(let o of[...new Set([...i.values.keys(),...a.values.keys()])].sort()){let l=i.values.get(o),p=a.values.get(o),u=`$.rules[${o}]`;if(!l&&p){p.allowed===!1&&b(e,{kind:"deny-added",path:u,classification:"strengthening",message:"A denied dependency edge was added.",after:p});continue}if(l&&!p){l.allowed===!1&&b(e,{kind:"deny-removed",path:u,classification:"weakening",message:"A denied dependency edge was removed.",before:l});continue}if(!l||!p)continue;l.allowed!==p.allowed&&b(e,{kind:p.allowed?"deny-disabled":"deny-enabled",path:`${u}.allowed`,classification:p.allowed?"weakening":"strengthening",message:p.allowed?"A previously denied dependency edge is now allowed.":"A dependency edge is now denied.",before:l.allowed,after:p.allowed});let m=l.peerIsolation===!0,g=p.peerIsolation===!0;if(m!==g){let c=l.from===l.to&&p.from===p.to;b(e,{kind:g?"peer-isolation-enabled":"peer-isolation-disabled",path:`${u}.peerIsolation`,classification:c?g?"strengthening":"weakening":"judgment-required",message:c?g?"Cross-slice dependencies inside this layer are now denied.":"Cross-slice dependencies inside this layer are no longer denied.":"Changing peer isolation on a cross-layer edge changes the denial scope.",before:m,after:g})}S(l.sliceFolders).join("\0")!==S(p.sliceFolders).join("\0")&&b(e,{kind:"slice-folders-changed",path:`${u}.sliceFolders`,classification:"judgment-required",message:"Slice ownership folders changed and can reclassify existing dependencies.",before:S(l.sliceFolders),after:S(p.sliceFolders)})}}function at(e,t,n){let r=t.safety??{},i=n.safety??{};for(let a of["maxTsSuppressions","maxAnyCasts"]){let o=r[a]??0,l=i[a]??0;o!==l&&b(e,{kind:l>o?"threshold-raised":"threshold-lowered",path:`$.safety.${a}`,classification:l>o?"weakening":"strengthening",message:l>o?"The safety threshold allows more violations.":"The safety threshold allows fewer violations.",before:o,after:l})}for(let a of["allowInMemory","allowDisabledPeerIsolation"])Y(e,`$.safety.${a}`,r[a]===!0,i[a]===!0,"weakening","A safety exception was enabled.","A safety exception was disabled.")}function st(e){return e.some(t=>t.classification==="weakening")?"weakening":e.some(t=>t.classification==="judgment-required")?"judgment-required":e.some(t=>t.classification==="strengthening")?"strengthening":"neutral"}function ve(e,t){let n=[];R(n,"$.include",e.include,t.include,{added:"strengthening",removed:"weakening",addedMessage:"Additional project roots are governed.",removedMessage:"Project roots were removed from governance."}),R(n,"$.exclude",e.exclude,t.exclude,{added:"weakening",removed:"strengthening",addedMessage:"Additional project paths are excluded from governance.",removedMessage:"Fewer project paths are excluded from governance."}),R(n,"$.dynamicImportAllowlist",e.dynamicImportAllowlist,t.dynamicImportAllowlist,{added:"weakening",removed:"strengthening",addedMessage:"Additional files may use non-literal dynamic imports.",removedMessage:"Fewer files may use non-literal dynamic imports."}),Y(n,"$.excludeGenerated",e.excludeGenerated!==!1,t.excludeGenerated!==!1,"weakening","Generated source is now excluded from governance.","Generated source is now governed.");let r={off:0,soft:1,"framework-soft":1,strict:2},i=e.cyclePolicy??"strict",a=t.cyclePolicy??"strict";if(i!==a){let o=r[a]===r[i]?"judgment-required":r[a]>r[i]?"strengthening":"weakening";b(n,{kind:"cycle-policy-changed",path:"$.cyclePolicy",classification:o,message:"The cycle enforcement level changed.",before:i,after:a})}return(e.frameworkOverlay??null)!==(t.frameworkOverlay??null)&&b(n,{kind:"framework-overlay-changed",path:"$.frameworkOverlay",classification:"judgment-required",message:"The framework overlay changed and may alter effective layer matching.",before:e.frameworkOverlay??null,after:t.frameworkOverlay??null}),rt(n,e.layers,t.layers),it(n,e.rules,t.rules),at(n,e,t),n.sort((o,l)=>o.path.localeCompare(l.path)||o.id.localeCompare(l.id)),{schemaVersion:Re,classification:st(n),findings:n}}function _e(e,t){if(!e||e.schemaVersion!==Re||typeof e.basePolicyHash!="string"||typeof e.candidatePolicyHash!="string"||typeof e.reason!="string"||!Array.isArray(e.findingIds)||e.findingIds.some(i=>typeof i!="string")||e.reason.trim().length===0||e.basePolicyHash!==t.basePolicyHash||e.candidatePolicyHash!==t.candidatePolicyHash)return!1;let n=S(e.findingIds),r=S(t.findingIds);return n.length===r.length&&n.every((i,a)=>i===r[a])}function v(e){let t=[];for(let n of e.replace(/\\/g,"/").split("/"))!n||n==="."||(n===".."&&t.length>0&&t.at(-1)!==".."?t.pop():t.push(n));return t.join("/")}function Me(e){return e!==void 0&&/[A-Za-z0-9_$]/.test(e)}function L(e,t){for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t}function H(e,t){let n=e[t];if(n!=="'"&&n!=='"')return;let r=t,i="";for(t+=1;t<e.length;t+=1){let a=e[t];if(a===n)return{value:i,offset:r,excerpt:e.slice(r,t+1)};a==="\\"&&t+1<e.length?(i+=e[t+1],t+=1):i+=a}}function E(e,t,n){return e.startsWith(t,n)&&!Me(e[n-1])&&!Me(e[n+t.length])}function ot(e,t){if(t=L(e,t+6),e[t]==="(")return H(e,L(e,t+1));let n=!1;if(E(e,"type",t)){let i=L(e,t+4);e[i]!==","&&!E(e,"from",i)&&(n=!0)}let r=Ne(e,t,!0);return r&&n?{...r,typeOnly:!0}:r}function ct(e,t){t=t+6;let n=L(e,t),r=!1;if(E(e,"type",n)){let a=L(e,n+4);(e[a]==="{"||e[a]==="*")&&(r=!0)}let i=Ne(e,t,!1);return i&&r?{...i,typeOnly:!0}:i}function Ne(e,t,n){for(;t<e.length;t+=1){if(e[t]===";")return;if(E(e,"from",t))return H(e,L(e,t+4));if(n&&(e[t]==="'"||e[t]==='"'))return H(e,t);if(t>0&&(E(e,"import",t)||E(e,"export",t)))return}}function lt(e,t){for(t+=1;t<e.length;t+=1){let n=e[t];if(n==="\\")t+=1;else if(n==="`")return t}return e.length}function pt(e,t){let n=t-1;for(;n>=0&&/\s/.test(e[n]);)n-=1;if(e[n]===".")return;let r=L(e,t+7);if(e[r]!=="(")return;r=L(e,r+1);let i=H(e,r);return i?{...i,requireCall:!0}:void 0}function dt(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r==="/"&&e[n+1]==="/"){if(n=e.indexOf(`
8
+ `,n+2),n<0)break;continue}if(r==="/"&&e[n+1]==="*"){let a=e.indexOf("*/",n+2);if(a<0)break;n=a+1;continue}if(r==="`"){n=lt(e,n);continue}if(r==="'"||r==='"'){let a=H(e,n);a&&(n=a.offset+a.excerpt.length-1);continue}let i=r==="i"&&E(e,"import",n)?ot(e,n):r==="e"&&E(e,"export",n)?ct(e,n):r==="r"&&E(e,"require",n)?pt(e,n):void 0;i&&t.push(i)}return t}function Te(e,t){let n=[],r=[];for(let i of dt(e.content)){let a=i.value;if(i.requireCall&&a.startsWith("."))continue;if(!a.startsWith(".")){if(i.typeOnly)continue;let u=j(a);if(!u)continue;let m=e.content.slice(0,i.offset).split(`
9
+ `).length;r.push({file:e.path,symbol:a,capability:u,evidence:{kind:"import",file:e.path,line:m,excerpt:i.excerpt}});continue}let o=e.content.slice(0,i.offset).split(`
10
+ `).length,l={kind:"import",file:e.path,line:o,excerpt:i.excerpt},p=ft(e.path,a,t);n.push({from:e.path,specifier:a,to:p?.path??null,resolution:p?"resolved":"unresolved",fromLayer:e.layer,toLayer:p?.layer??null,evidence:l})}return{edges:n,capabilityUses:r}}function ft(e,t,n){let r=e.split("/");r.pop();for(let a of t.split("/"))a==="."||a===""||(a===".."?r.pop():r.push(a));let i=r.join("/");for(let a of[i,`${i}.ts`,`${i}.tsx`,`${i}.mts`,`${i}.cts`,`${i}/index.ts`,`${i}/index.tsx`]){let o=n.get(a);if(o)return o}}function De(e,t){let n=[];for(let r of e){if(!r.to||!r.fromLayer||!r.toLayer)continue;let i=G(t.rules,r.fromLayer,r.toLayer,{fromPath:r.from,toPath:r.to,layers:t.layers});i&&n.push({ruleId:`layer-dependency:${i.from}->${i.to}`,message:i.message??`${i.from} must not depend on ${i.to}.`,edge:r,evidence:r.evidence})}return n}function ye(e,t){let n=typeof e=="string"?Pe(e,t):ge(e,t);return{...n,policyHash:$(x(n.config))}}function gt(e){let t=ye(e.baseConfig,e.baseSource??"base ark.config.json"),n=ye(e.candidateConfig,e.candidateSource??"candidate ark.config.json"),r=ve(t.config,n.config),i=r.findings.filter(l=>l.classification==="weakening"||l.classification==="judgment-required").map(l=>l.id).sort(),a=i.length>0,o=a&&_e(e.acknowledgement,{basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,findingIds:i});return{schemaVersion:r.schemaVersion,basePolicyHash:t.policyHash,candidatePolicyHash:n.policyHash,classification:r.classification,findings:r.findings,blockingFindingIds:i,requiresAcknowledgement:a,acknowledged:o,valid:!a||o}}function J(e){let t=e.files.map(l=>{let p=v(l.path);return{path:p,content:l.content,contentHash:$(l.content),layer:_(p,e.contract.config.layers)??null}}).sort((l,p)=>l.path.localeCompare(p.path)),n=new Map(t.map(l=>[l.path,l])),r=[],i=[];for(let l of t){let p=Te(l,n);r.push(...p.edges),i.push(...p.capabilityUses)}let a=De(r,e.contract.config),o=new Map(e.contract.config.layers.map(l=>[l.name,new Set(z(l))]));for(let l of i){let p=n.get(l.file)?.layer;!p||!o.get(p)?.has(l.capability)||a.push({ruleId:"CAPABILITY_VIOLATION",message:`${p} denies the ${l.capability} capability; found import of "${l.symbol}".`,capability:l.capability,symbol:l.symbol,evidence:l.evidence})}return{ir:{schemaVersion:"1.0",policyHash:e.contract.policyHash,compilerOptionsHash:$(x(e.compilerOptions??{})),files:t,layers:e.contract.config.layers.map(l=>l.name),edges:r,capabilityUses:i,violations:a}}}function me(e){let t=new Map(e.files.map(n=>[v(n.path),n]));for(let n of e.changes){let r=v(n.path);"delete"in n&&n.delete?t.delete(r):"content"in n&&t.set(r,{path:r,content:n.content})}return J({contract:e.contract,files:[...t.values()],compilerOptions:e.compilerOptions})}function yt(e){let t=`${e.evidence.file}:${e.evidence.line}`;if(!e.edge)return`${e.ruleId} at ${t}: ${e.message}`;let n=e.edge.to??e.edge.specifier;return`${e.ruleId} at ${t}: ${e.edge.from} imports ${n}. ${e.message}`}function Fe(e){let t=0,n=new Map,r=new Map,i=new Set,a=[],o=[],l=p=>{n.set(p,t),r.set(p,t),t+=1,a.push(p),i.add(p);for(let g of[...e.get(p)??[]].sort())e.has(g)&&(n.has(g)?i.has(g)&&r.set(p,Math.min(r.get(p)??0,n.get(g)??0)):(l(g),r.set(p,Math.min(r.get(p)??0,r.get(g)??0))));if(r.get(p)!==n.get(p))return;let u=[],m;do{if(m=a.pop(),m===void 0)break;i.delete(m),u.push(m)}while(m!==p);u.length>1&&o.push(u.sort())};for(let p of[...e.keys()].sort())n.has(p)||l(p);return o.sort((p,u)=>p[0].localeCompare(u[0])).map(p=>({ruleId:"CIRCULAR_DEPENDENCY",file:p[0],line:1,target:p.join(" \u2192 "),message:`Circular dependency among ${p.length} files: ${p.join(" \u2192 ")} \u2192 ${p[0]}.`,cycleKind:"value"}))}function he(e){let t=e.contentViolations.map(a=>({...a})),n=(e.warnings??[]).map(a=>({...a})),r=new Map(e.files.map(a=>[a,new Set]));for(let a of e.edges){if(a.to&&a.to!==a.from&&!a.typeOnly&&r.has(a.from)&&r.get(a.from)?.add(a.to),!a.to||!a.toLayer)continue;let o=G(e.rules,a.fromLayer,a.toLayer,{fromPath:a.from,toPath:a.to,layers:e.config.layers});if(!o)continue;let l=!!o.peerIsolation;t.push({ruleId:"LAYER_IMPORT_VIOLATION",file:a.from,line:a.line,fromLayer:a.fromLayer,toLayer:a.toLayer,target:a.to,...a.typeOnly?{typeOnly:!0}:{},...a.targetTypeOnlyExports?{targetTypeOnlyExports:!0}:{},...a.sourcePureTypeModule?{sourcePureTypeModule:!0}:{},...a.namedBindingsTypeOnly?{namedBindingsTypeOnly:!0}:{},...!l&&a.portProofEligible?{portProofEligible:!0}:{},...a.kind?{edgeKind:a.kind}:{},...l?{peerIsolation:!0}:{},message:o.message??(l?`${a.fromLayer} must not ${a.kind} another slice of ${a.toLayer} (${a.from} \u2192 ${a.to}). Extract shared code or use events/ports across slices.`:`${a.fromLayer} must not ${a.kind} ${a.toLayer}.`)})}let i=String(e.config.cyclePolicy??"strict").toLowerCase();if(i!=="off"){let a=Fe(r);i==="soft"||i==="framework-soft"?n.push(...a.map(o=>({...o,message:`${o.message} (soft cycle policy \u2014 advisory only; set cyclePolicy: "strict" to fail the check)`,failsStrict:!1}))):t.push(...a)}return{violations:t,warnings:n,safety:e.safety}}function je(e){switch(e.ruleId){case"LAYER_IMPORT_VIOLATION":return e.typeOnly||e.targetTypeOnlyExports||e.namedBindingsTypeOnly?"Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.":e.peerIsolation?"Extract the shared dependency to a shared layer, then preflight again.":`Define a port in ${e.fromLayer??"the source layer"}, inject the ${e.toLayer??"outer-layer"} implementation, then preflight again.`;case"FORBIDDEN_GLOBAL":return`Inject ${e.target??"the capability"} through a port, then preflight again.`;case"CAPABILITY_VIOLATION":return`Define a ${String(e.capability??"capability")} port in ${e.fromLayer??"the walled layer"}, bind the implementation outside it, then preflight again.`;case"CIRCULAR_DEPENDENCY":return"Extract the shared dependency into a third module, then preflight again.";case"RAW_EVENT_PUBLISH":return"Publish through a registered intent creator, then run Ark again.";case"PUBLISH_MISSING_SOURCE":return"Add metadata.source to the publish call, then run Ark again.";default:return`Resolve ${typeof e.ruleId=="string"&&e.ruleId.length>0?e.ruleId:"ARK_UNKNOWN"} without weakening ark.config.json, then run Ark again.`}}function Ve(e){return $(x(e.map(({path:t,contentHash:n})=>({path:t,contentHash:n}))))}function mt(e){let t=J(e),n=new Map(t.ir.files.map(s=>[s.path,s])),r=[],i=new Set,a=[];for(let s of e.changes){let d=s.path.replace(/\\/g,"/"),f=v(s.path);if(!f||f===".."||f.startsWith("../")||d.startsWith("/")||/^[A-Za-z]:\//.test(d)||d.includes("\0")){a.push({ruleId:"INVALID_CHANGE_PATH",file:"<change-set>",line:1,message:"Every change requires a safe, non-empty project-relative path."});continue}if(i.has(f)){a.push({ruleId:"DUPLICATE_CHANGE_PATH",file:f,line:1,message:`The atomic change set contains more than one operation for ${f}.`});continue}i.add(f),"delete"in s&&s.delete&&!n.has(f)&&a.push({ruleId:"DELETE_TARGET_MISSING",file:f,line:1,message:`Cannot delete ${f} because it is not present in the supplied base tree.`}),r.push("delete"in s&&s.delete?{path:f,delete:!0}:{path:f,content:"content"in s?s.content:""})}e.changes.length===0&&a.push({ruleId:"CHANGE_SET_EMPTY",file:"<change-set>",line:1,message:"Atomic preflight requires at least one create, update, or delete."});let o=me({...e,changes:r}),l=new Map(o.ir.files.map(s=>[s.path,s])),p=o.ir.violations.filter(s=>s.ruleId==="CAPABILITY_VIOLATION").map(s=>({ruleId:s.ruleId,file:s.evidence.file,line:s.evidence.line,target:s.symbol,capability:s.capability,message:s.message})),u=he({config:e.contract.config,rules:e.contract.config.rules,files:o.ir.files.map(s=>s.path),contentViolations:p,edges:o.ir.edges.filter(s=>!!s.fromLayer).map(s=>({from:s.from,fromLayer:s.fromLayer,...s.to?{to:s.to}:{},...s.toLayer?{toLayer:s.toLayer}:{},line:s.evidence.line,kind:"import"}))}),m=r.map(s=>{let d=v(s.path),f=n.get(d),y=l.get(d);return{path:d,operation:"delete"in s&&s.delete?"delete":f?"update":"create",...f?{beforeContentHash:f.contentHash}:{},...y?{candidateContentHash:y.contentHash}:{}}}).sort((s,d)=>s.path.localeCompare(d.path)),g=[...a,...u.violations].map(s=>({...s,nextAction:je(s)})),c=e.changeMap?re({changeMap:e.changeMap,changes:m,baseDependencies:t.ir.edges.flatMap(s=>s.to?[{from:s.from,to:s.to}]:[]),candidateDependencies:o.ir.edges.flatMap(s=>s.to?[{from:s.from,to:s.to}]:[])}):void 0;return{schemaVersion:"1.0",valid:g.length===0&&(c?.structurallyConverged??!0),readOnly:!0,policyHash:e.contract.policyHash,compilerOptionsHash:o.ir.compilerOptionsHash,baseTreeHash:Ve(t.ir.files),candidateTreeHash:Ve(o.ir.files),...e.changeMap?{changeMapHash:e.changeMap.hash}:{},...c?{convergence:c}:{},changes:m,violations:g,warnings:u.warnings}}function I(e,t,n={}){return{ruleId:e,message:t,...n}}function ht(e){let{config:t,rules:n,files:r,manifest:i}=e,a=[];if(t.dynamicImportAllowlist!==void 0&&(!Array.isArray(t.dynamicImportAllowlist)||t.dynamicImportAllowlist.some(s=>typeof s!="string"))&&a.push(I("CONFIG_INVALID_DYNAMIC_IMPORT_ALLOWLIST","dynamicImportAllowlist must be an array of file globs.")),t.safety!==void 0&&(t.safety===null||typeof t.safety!="object"||Array.isArray(t.safety)))a.push(I("CONFIG_INVALID_SAFETY","safety must be an object."));else if(t.safety)for(let s of["maxTsSuppressions","maxAnyCasts"]){let d=t.safety[s];d!==void 0&&(!Number.isInteger(d)||d<0)&&a.push(I("CONFIG_INVALID_SAFETY_THRESHOLD",`safety.${s} must be a non-negative integer.`))}let o=Array.isArray(t.layers)?t.layers:[],l=Array.isArray(i?.architecture?.layers)?i.architecture.layers:[],p=new Set([...o.map(s=>s.name).filter(Boolean),...l.map(s=>s.name).filter(s=>!!s)]);o.length===0&&a.push(I("CONFIG_NO_LAYERS","No file layers are configured; ark-check cannot classify files for import-boundary enforcement."));let u=new Set,m=new Set;for(let s of o){if(!s.name){a.push(I("CONFIG_LAYER_WITHOUT_NAME","A configured layer is missing a name."));continue}u.has(s.name)&&m.add(s.name),u.add(s.name),s.forbiddenGlobals!==void 0&&(!Array.isArray(s.forbiddenGlobals)||s.forbiddenGlobals.some(f=>typeof f!="string"))&&a.push(I("CONFIG_INVALID_FORBIDDEN_GLOBALS",`Layer "${s.name}" has an invalid forbiddenGlobals value; expected an array of strings (e.g. ["fetch", "Date.now"]). The entry is ignored.`,{layer:s.name}));let d=Array.isArray(s.patterns)?s.patterns:[];if(d.length===0){a.push(I("CONFIG_LAYER_WITHOUT_PATTERNS",`Layer "${s.name}" has no file patterns and will never classify files.`,{layer:s.name}));continue}for(let f of d){let y;try{y=D(f)}catch(h){a.push(I("CONFIG_INVALID_LAYER_PATTERN",`Layer "${s.name}" has an invalid pattern "${f}": ${h instanceof Error?h.message:String(h)}`,{layer:s.name,pattern:f}));continue}!r.some(h=>y.test(h))&&!s.optional&&a.push(I("CONFIG_LAYER_PATTERN_NO_MATCHES",`Layer "${s.name}" pattern "${f}" matched no included files.`,{layer:s.name,pattern:f,failsStrict:!1}))}}for(let s of m)a.push(I("CONFIG_DUPLICATE_LAYER",`Layer "${s}" is configured more than once.`,{layer:s}));if(p.size>0)for(let s of n??[])s.from&&!p.has(s.from)&&a.push(I("CONFIG_RULE_UNKNOWN_FROM_LAYER",`Rule references unknown source layer "${s.from}".`,{fromLayer:s.from,toLayer:s.to})),s.to&&!p.has(s.to)&&a.push(I("CONFIG_RULE_UNKNOWN_TO_LAYER",`Rule references unknown target layer "${s.to}".`,{fromLayer:s.from,toLayer:s.to}));let g=new Set;if(o.length>1)for(let s of r){let d=-1,f=[];for(let y of o)for(let h of y.patterns??[]){if(!D(h).test(s))continue;let A=Q(h);A>d?(d=A,f=[y.name]):A===d&&!f.includes(y.name)&&f.push(y.name)}f.length>1&&g.add([...f].sort().join(" + "))}g.size>0&&a.push(I("CONFIG_AMBIGUOUS_LAYERS",`Some files match multiple layers at equal specificity; classification falls back to declaration order. Disambiguate the overlapping patterns: ${[...g].join(", ")}.`,{pairs:[...g]}));let c=r.filter(s=>!_(s,o));return c.length>0&&a.push(I("CONFIG_UNCLASSIFIED_FILES",`${c.length} included source file(s) are not matched by any configured layer; ark-check will not enforce import rules for those source files.`,{count:c.length,samples:c.slice(0,5)})),a}export{de as AMBIENT_CAPABILITY_ENTRIES,pe as CAPABILITY_IDS,ce as SOURCE_POLICY_MESSAGES,We as ambientCoveredByForbiddenGlobals,re as analyzeArchitectureConvergence,me as analyzeChange,gt as analyzePolicyDelta,J as analyzeProject,V as capabilityForAmbientName,j as capabilityForModuleSpecifier,Ye as classifyPublishFacts,ht as collectAnalysisConfigWarnings,Ze as collectCapabilityUses,oe as collectForbiddenCapabilityUses,Fe as detectArchitectureCycles,z as effectiveCapabilityDeny,he as evaluateArchitectureGraph,yt as explainViolation,se as extractSemanticDependencies,ze as loadArchitectureChangeMap,ye as loadContract,xe as looksLikeArkIntent,Je as lowerForbiddenGlobal,q as loweredLayerCoverage,mt as preflightChange};
@@ -29,7 +29,10 @@ import {
29
29
  collectConfigWarnings,
30
30
  } from './config-warnings.mjs';
31
31
  import {
32
+ ambientCoveredByForbiddenGlobals,
33
+ collectCapabilityUses,
32
34
  collectForbiddenCapabilityUses,
35
+ effectiveCapabilityDeny,
33
36
  evaluateArchitectureGraph,
34
37
  extractSemanticDependencies,
35
38
  } from './analysis-engine.mjs';
@@ -69,6 +72,34 @@ export function scanSourceFile(ts, root, config, rules, manifestIntentLayers, fi
69
72
  });
70
73
  }
71
74
 
75
+ // U04 — opted-in capability walls (ADR 0009). One violation, one voice: an
76
+ // ambient use already covered by this layer's forbiddenGlobals reports only
77
+ // FORBIDDEN_GLOBAL (D7 dedup); absence of the surface adds nothing.
78
+ const capabilityDeny = new Set(effectiveCapabilityDeny(layerConfig ?? {}));
79
+ if (capabilityDeny.size > 0) {
80
+ for (const use of collectCapabilityUses(ts, sourceFile)) {
81
+ if (!capabilityDeny.has(use.capability)) continue;
82
+ if (
83
+ use.source === 'ambient-global' &&
84
+ ambientCoveredByForbiddenGlobals(use.symbol, forbiddenGlobals)
85
+ ) {
86
+ continue;
87
+ }
88
+ violations.push({
89
+ ruleId: 'CAPABILITY_VIOLATION',
90
+ file: normalize(path.relative(root, file)),
91
+ line: use.line,
92
+ fromLayer: sourceLayer,
93
+ target: use.symbol,
94
+ capability: use.capability,
95
+ message:
96
+ use.source === 'import-based'
97
+ ? `${sourceLayer} denies the ${use.capability} capability; found import of "${use.symbol}".`
98
+ : `${sourceLayer} denies the ${use.capability} capability; found ambient "${use.symbol}".`,
99
+ });
100
+ }
101
+ }
102
+
72
103
  const checkModuleEdge = (specifier, node, kind, typeOnly = false) => {
73
104
  const namedBindings = namedModuleBindings(ts, node);
74
105
  edges.push({
@@ -113,6 +113,31 @@ export const ARK_CONFIG_SCHEMA = {
113
113
  intentPrefixes: stringArraySchema,
114
114
  description: { type: 'string', minLength: 1 },
115
115
  forbiddenGlobals: stringArraySchema,
116
+ capabilities: {
117
+ type: 'object',
118
+ additionalProperties: false,
119
+ properties: {
120
+ deny: {
121
+ type: 'array',
122
+ uniqueItems: true,
123
+ items: {
124
+ type: 'string',
125
+ // Parity with src/domain/capabilities.ts CAPABILITY_IDS (guarded by tests;
126
+ // this literal keeps the generated CLI artifact self-contained).
127
+ enum: [
128
+ 'network',
129
+ 'filesystem',
130
+ 'clock',
131
+ 'randomness',
132
+ 'environment',
133
+ 'process',
134
+ 'persistence',
135
+ ],
136
+ },
137
+ },
138
+ },
139
+ },
140
+ pure: { type: 'boolean' },
116
141
  mayImportInfrastructure: { type: 'boolean' },
117
142
  optional: { type: 'boolean' },
118
143
  },
@@ -11,6 +11,12 @@
11
11
  * Q03 golden-pattern precedent) so the versioned config contract is untouched.
12
12
  * A malformed ack file (or a malformed edge inside it) never suppresses a smell.
13
13
  *
14
+ * X02 — acks have a lifecycle: an optional `reviewBy` (YYYY-MM-DD) marks when a
15
+ * deliberate exception must be re-reviewed. Past that date the ack stops
16
+ * applying and the smell returns annotated — migration acks cannot fossilize.
17
+ * Undated acks keep applying (backward compatible) but are counted and
18
+ * reported so they can be given a date.
19
+ *
14
20
  * Known limit (documented, deliberate): layer roles are inferred from layer NAMES
15
21
  * via substring heuristics — a name like "Auditorium" reads as audit-ish. The
16
22
  * surface is advisory, so a miss costs a warning line, never a verdict.
@@ -49,6 +55,29 @@ const MAX_MESSAGE_EDGES = 6;
49
55
  const PERIPHERAL_LAYER_RE = /observab|audit|telemetry|monitor|logging|metric|tracing/i;
50
56
  const CORE_TARGET_RE = /application|orchestr|persist|repositor/i;
51
57
  const ADAPTER_LAYER_RE = /adapter|persist|integrat|infra|gateway/i;
58
+ const FAMILY_INFRA_RE = /^(infra(structure)?|base|core|shared|common|kernel|platform|foundation)$/i;
59
+
60
+ /** Split a layer name into words: camelCase boundaries, digits, delimiters. */
61
+ function nameTokens(name) {
62
+ return String(name).match(/[A-Z]?[a-z]+|[A-Z]+(?![a-z])|\d+/g) ?? [];
63
+ }
64
+
65
+ /**
66
+ * X03 — an adapter reaching its OWN family's infrastructure base is not a
67
+ * lateral peer: same leading family token and EVERY remaining target token
68
+ * reads as an infra base (Infra/Base/Core/Shared/…) — `PaymentsCoreAdapters`
69
+ * is still a sibling, not a base. Field origin: amarilla, where
70
+ * `<Family>Adapters -> <Family>Infra` fired as adapter-to-adapter.
71
+ * Name heuristic like the role regexes above — a miss costs a warning line.
72
+ */
73
+ function isFamilyInfrastructureEdge(from, to) {
74
+ const fromTokens = nameTokens(from);
75
+ const toTokens = nameTokens(to);
76
+ if (fromTokens.length === 0 || toTokens.length < 2) return false;
77
+ const family = toTokens[0];
78
+ if (family.length < 2 || family.toLowerCase() !== fromTokens[0].toLowerCase()) return false;
79
+ return toTokens.slice(1).every((t) => FAMILY_INFRA_RE.test(t));
80
+ }
52
81
 
53
82
  /** Collision-safe internal key for a directed edge (layer names are arbitrary strings). */
54
83
  function directedKey(from, to) {
@@ -100,9 +129,12 @@ export function loadContractSmellAcks(root) {
100
129
  typeof a === 'object' &&
101
130
  typeof a.id === 'string' &&
102
131
  typeof a.edge === 'string' &&
103
- a.edge.trim().length > 0
132
+ a.edge.trim().length > 0 &&
133
+ (a.reviewBy === undefined || typeof a.reviewBy === 'string')
104
134
  );
105
- if (!wellFormed) return invalid('every ack needs string id and non-empty string edge');
135
+ if (!wellFormed) {
136
+ return invalid('every ack needs string id, non-empty string edge, and string reviewBy when present');
137
+ }
106
138
  return { path: relPath, exists: true, acks };
107
139
  }
108
140
 
@@ -121,10 +153,44 @@ function normalizeAckEdge(id, edge) {
121
153
  return raw;
122
154
  }
123
155
 
124
- function isAcknowledged(ackState, id, canonicalEdge) {
125
- if (!ackState || ackState.invalid || !Array.isArray(ackState.acks)) return false;
126
- if (canonicalEdge == null) return false;
127
- return ackState.acks.some((a) => a.id === id && normalizeAckEdge(id, a.edge) === canonicalEdge);
156
+ /**
157
+ * X02 lifecycle status of one ack entry. `undated` and `current` apply
158
+ * (suppress); `expired` and `malformed` do not — fail-loud like a sloppy edge.
159
+ * Strict round-trip date check: `2026-02-30` must not pass as valid.
160
+ */
161
+ function ackLifecycleStatus(ack, today) {
162
+ const rb = ack.reviewBy;
163
+ if (rb === undefined || rb === null) return 'undated';
164
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(rb)) return 'malformed';
165
+ const parsed = new Date(`${rb}T00:00:00.000Z`);
166
+ if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== rb) {
167
+ return 'malformed';
168
+ }
169
+ return typeof today === 'string' && rb < today ? 'expired' : 'current';
170
+ }
171
+
172
+ /**
173
+ * Match a detected edge against the ack sidecar with lifecycle applied.
174
+ * Returns how the edge resolves: `none` (no matching ack), an applying status
175
+ * (`current` | `undated`), or a non-applying one (`expired` | `malformed`,
176
+ * with the reviewBy that failed). Once ANY dated ack exists for the edge, the
177
+ * dated entries govern — a leftover undated duplicate cannot resurrect an
178
+ * expired exception. Among dated entries a fresh re-ack wins over a dead one.
179
+ */
180
+ function resolveAck(ackState, id, canonicalEdge, today) {
181
+ if (!ackState || ackState.invalid || !Array.isArray(ackState.acks)) return { status: 'none' };
182
+ if (canonicalEdge == null) return { status: 'none' };
183
+ let dead = null;
184
+ let undated = false;
185
+ for (const a of ackState.acks) {
186
+ if (a.id !== id || normalizeAckEdge(id, a.edge) !== canonicalEdge) continue;
187
+ const status = ackLifecycleStatus(a, today);
188
+ if (status === 'current') return { status };
189
+ if (status === 'undated') undated = true;
190
+ else dead ??= { status, reviewBy: a.reviewBy };
191
+ }
192
+ if (dead) return dead;
193
+ return undated ? { status: 'undated' } : { status: 'none' };
128
194
  }
129
195
 
130
196
  /**
@@ -140,7 +206,8 @@ export function analyzeContractSmells(
140
206
  config,
141
207
  coverage = null,
142
208
  ackState = { exists: false, acks: [] },
143
- effectiveRules = null
209
+ effectiveRules = null,
210
+ today = null
144
211
  ) {
145
212
  const layers = Array.isArray(config?.layers) ? config.layers : [];
146
213
  const rules = wellFormedRules(config, effectiveRules);
@@ -188,10 +255,12 @@ export function analyzeContractSmells(
188
255
  }
189
256
  }
190
257
 
191
- // 3) Lateral adapter-to-adapter explicit allows. Skip only edges the peripheral
192
- // sensor already flagged (peripheral source AND core-ish target).
258
+ // 3) Lateral adapter-to-adapter explicit allows. Skip edges the peripheral
259
+ // sensor already flagged (peripheral source AND core-ish target), and
260
+ // X03: an adapter reaching its own family's infra base is not a peer.
193
261
  for (const r of explicitAllows) {
194
262
  if (PERIPHERAL_LAYER_RE.test(r.from) && CORE_TARGET_RE.test(r.to)) continue;
263
+ if (isFamilyInfrastructureEdge(r.from, r.to)) continue;
195
264
  if (ADAPTER_LAYER_RE.test(r.from) && ADAPTER_LAYER_RE.test(r.to)) {
196
265
  add(
197
266
  'contract-lateral-adapter-allow',
@@ -223,6 +292,7 @@ export function analyzeContractSmells(
223
292
 
224
293
  const smells = [];
225
294
  let matchedAcks = 0;
295
+ const ackLifecycle = { undated: 0, malformed: 0, expired: [] };
226
296
  for (const id of CONTRACT_SMELL_IDS) {
227
297
  const entries = findings[id];
228
298
  if (!entries || entries.length === 0) continue;
@@ -232,8 +302,20 @@ export function analyzeContractSmells(
232
302
  for (const entry of entries) {
233
303
  if (seenDetail.has(entry.detail)) continue;
234
304
  seenDetail.add(entry.detail);
235
- if (isAcknowledged(ackState, id, entry.edge)) {
305
+ const ack = resolveAck(ackState, id, entry.edge, today);
306
+ if (ack.status === 'current' || ack.status === 'undated') {
236
307
  acknowledgedEdges += 1;
308
+ if (ack.status === 'undated') ackLifecycle.undated += 1;
309
+ continue;
310
+ }
311
+ if (ack.status === 'expired') {
312
+ ackLifecycle.expired.push({ id, edge: entry.edge, reviewBy: ack.reviewBy });
313
+ kept.push({ ...entry, detail: `${entry.detail} (ack expired ${ack.reviewBy})` });
314
+ continue;
315
+ }
316
+ if (ack.status === 'malformed') {
317
+ ackLifecycle.malformed += 1;
318
+ kept.push({ ...entry, detail: `${entry.detail} (ack review-by malformed)` });
237
319
  continue;
238
320
  }
239
321
  kept.push(entry);
@@ -255,20 +337,23 @@ export function analyzeContractSmells(
255
337
  acknowledgedEdges,
256
338
  });
257
339
  }
258
- return { smells, matchedAcks };
340
+ return { smells, matchedAcks, ackLifecycle };
259
341
  }
260
342
 
261
343
  /**
262
344
  * Detect contract smells (compat wrapper over analyzeContractSmells).
345
+ * Defaults `today` to the real clock so expired acks stop applying on every
346
+ * public path, not only through the doctor; pass `null` to disable expiry.
263
347
  * @returns {Array<{id: string, severity: 'warn', message: string, outcome: string, evidence: string[], fix: string, acknowledgedEdges: number}>}
264
348
  */
265
349
  export function detectContractSmells(
266
350
  config,
267
351
  coverage = null,
268
352
  ackState = { exists: false, acks: [] },
269
- effectiveRules = null
353
+ effectiveRules = null,
354
+ today = todayUtc()
270
355
  ) {
271
- return analyzeContractSmells(config, coverage, ackState, effectiveRules).smells;
356
+ return analyzeContractSmells(config, coverage, ackState, effectiveRules, today).smells;
272
357
  }
273
358
 
274
359
  function messageFor(id, entries) {
@@ -399,6 +484,11 @@ export function computeGovernanceWeight(config, coverage = null, effectiveRules
399
484
  };
400
485
  }
401
486
 
487
+ /** Today as UTC YYYY-MM-DD — the only clock read; tests inject `today` instead. */
488
+ function todayUtc() {
489
+ return new Date().toISOString().slice(0, 10);
490
+ }
491
+
402
492
  /**
403
493
  * One-call compute for doctor: acks + smells + governance weight + JSON-ready summary.
404
494
  * `rules` should be the rules actually in force (manifest-aware callers pass them).
@@ -407,12 +497,19 @@ export function computeGovernanceWeight(config, coverage = null, effectiveRules
407
497
  * @param {object} config
408
498
  * @param {object|null} coverage
409
499
  * @param {object[]|null} [rules]
500
+ * @param {string} [today] UTC YYYY-MM-DD for ack lifecycle; defaults to the real clock
410
501
  */
411
- export function computeContractHealth(root, config, coverage, rules = null) {
502
+ export function computeContractHealth(root, config, coverage, rules = null, today = todayUtc()) {
412
503
  const ackState = loadContractSmellAcks(root);
413
- const { smells, matchedAcks } = analyzeContractSmells(config, coverage, ackState, rules);
504
+ const { smells, matchedAcks, ackLifecycle } = analyzeContractSmells(
505
+ config,
506
+ coverage,
507
+ ackState,
508
+ rules,
509
+ today
510
+ );
414
511
  return {
415
- ...summarizeContractHealth(smells, ackState, matchedAcks),
512
+ ...summarizeContractHealth(smells, ackState, matchedAcks, ackLifecycle),
416
513
  governanceWeight: computeGovernanceWeight(config, coverage, rules),
417
514
  smells,
418
515
  };
@@ -447,7 +544,13 @@ export function formatContractHealthLines(smells, health) {
447
544
  const list = smells ?? [];
448
545
  const gw = health?.governanceWeight;
449
546
  const weightNoteworthy = gw?.weight === 'heavy' || gw?.weight === 'light';
450
- if (list.length === 0 && !health?.ackFile?.invalid && !weightNoteworthy) return rows;
547
+ const lc = health?.ackLifecycle;
548
+ // Undated acks must surface even when every smell is suppressed — that is
549
+ // exactly the fossilization case X02 exists to catch.
550
+ const lifecycleNoteworthy = (lc?.undated ?? 0) > 0;
551
+ if (list.length === 0 && !health?.ackFile?.invalid && !weightNoteworthy && !lifecycleNoteworthy) {
552
+ return rows;
553
+ }
451
554
  if (health?.ackFile?.invalid) {
452
555
  rows.push({
453
556
  mark: 'warn',
@@ -470,6 +573,24 @@ export function formatContractHealthLines(smells, health) {
470
573
  if ((health?.acknowledged ?? 0) > 0) {
471
574
  rows.push({ mark: 'dim', text: `acknowledged edges applied: ${health.acknowledged}` });
472
575
  }
576
+ if ((lc?.expiredCount ?? 0) > 0) {
577
+ rows.push({
578
+ mark: 'warn',
579
+ text: `${lc.expiredCount} acknowledgment(s) past review-by — the smell is active again; re-review the edge and re-ack with a new date, or fix the contract.`,
580
+ });
581
+ }
582
+ if ((lc?.malformed ?? 0) > 0) {
583
+ rows.push({
584
+ mark: 'warn',
585
+ text: `${lc.malformed} acknowledgment(s) have a malformed review-by (expected YYYY-MM-DD) — they are ignored, not silently applied.`,
586
+ });
587
+ }
588
+ if ((lc?.undated ?? 0) > 0) {
589
+ rows.push({
590
+ mark: 'dim',
591
+ text: `${lc.undated} applied acknowledgment(s) have no review-by date — add one so migration acks cannot fossilize.`,
592
+ });
593
+ }
473
594
  if (weightNoteworthy) {
474
595
  rows.push({
475
596
  mark: 'warn',
@@ -487,18 +608,33 @@ export function formatContractHealthLines(smells, health) {
487
608
  /**
488
609
  * Contract-health summary for doctor JSON / human output. Advisory only.
489
610
  * `acknowledged` counts ack entries that MATCHED a detected edge (stale acks count 0).
611
+ * X02 — `ackLifecycle` reports how applied acks age: `undated` applied without
612
+ * a review-by, `expired` past it (no longer applied), `malformed` bad dates.
490
613
  *
491
614
  * @param {ReturnType<typeof detectContractSmells>} smells
492
615
  * @param {ReturnType<typeof loadContractSmellAcks>} ackState
493
616
  * @param {number} [matchedAcks]
617
+ * @param {{ undated: number, malformed: number, expired: Array<{id: string, edge: string, reviewBy: string}> }} [ackLifecycle]
494
618
  */
495
- export function summarizeContractHealth(smells, ackState = { exists: false, acks: [] }, matchedAcks = 0) {
619
+ export function summarizeContractHealth(
620
+ smells,
621
+ ackState = { exists: false, acks: [] },
622
+ matchedAcks = 0,
623
+ ackLifecycle = { undated: 0, malformed: 0, expired: [] }
624
+ ) {
496
625
  const list = Array.isArray(smells) ? smells : [];
626
+ const expired = Array.isArray(ackLifecycle?.expired) ? ackLifecycle.expired : [];
497
627
  return {
498
628
  status: list.length > 0 ? 'contract-smells' : 'ok',
499
629
  smellCount: list.length,
500
630
  ids: list.map((s) => s.id),
501
631
  acknowledged: ackState?.invalid ? 0 : matchedAcks,
632
+ ackLifecycle: {
633
+ undated: ackLifecycle?.undated ?? 0,
634
+ malformed: ackLifecycle?.malformed ?? 0,
635
+ expiredCount: expired.length,
636
+ expired: expired.slice(0, MAX_EVIDENCE),
637
+ },
502
638
  advisory: true,
503
639
  label:
504
640
  list.length > 0