Cleaning up test code and tying it to the testing "intent". The WebIDClaim is now very much a claim. the verify method returns the WebID if there are no errros (SAN not parsed, document not found, etc...) webid
authorHenry Story <henry.story@bblfish.net>
Sat, 03 Dec 2011 16:12:05 +0100
branchwebid
changeset 144 ff77c1ecc2b4
parent 143 704344e7f4d8
child 145 4865469e5aac
Cleaning up test code and tying it to the testing "intent". The WebIDClaim is now very much a claim. the verify method returns the WebID if there are no errros (SAN not parsed, document not found, etc...)
src/main/resources/template/WebId.xhtml
src/main/scala/WebCache.scala
src/main/scala/auth/Principals.scala
src/main/scala/auth/WebIdClaim.scala
src/main/scala/auth/X509Claim.scala
src/main/scala/auth/X509view.scala
src/main/scala/auth/earl.scala
--- a/src/main/resources/template/WebId.xhtml	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/resources/template/WebId.xhtml	Sat Dec 03 16:12:05 2011 +0100
@@ -5,35 +5,29 @@
     </head>
 <body>
   <h1>WebID Authentication Report</h1>
-      
-  <p>This page describes in detail the state of your <a href="http://webid.info/spec">WebID authentication</a> session on <span class="date">Thu Nov 17 08:31:53 PST 2011.</span> </p>
-  <p><a href="" onclick="return logout()">logout (only works on IE and Firefox)</a></p>
+  <p>This page describes in detail the state of your <a href="http://webid.info/spec">WebID authentication</a> session
+      on <span class="date">Thu Nov 17 08:31:53 PST 2011.</span> </p>
 
-  <h2>WebIDs</h2>
+  <p><a href="" onclick="return logout()">logout</a> -- (only works on IE and Firefox)</p>
 
-
+  <h2>WebID</h2>
   <div class="webid_tests">
-      <h3 class="webid">WebID found: http://example.com/#me</h3>
-  <table class="webid_test" width="100%" rules="groups">
-      <caption class="tst_question">Could at least one WebID claim be verified?</caption>
-      <thead><tr><td>description</td><td class="tst_txt">At least one WebID claimed in the certificate has public key that verifies.</td></tr></thead>
-      <tbody>
-      <tr><td>subject</td><td><a href="#cert" class="tst_sub">the certificate sent by your browser</a></td></tr>
-      <tr><td>test result</td><td><font color="green" class="tst_res">passed</font></td></tr>
-      <tr><td>result description</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+  <p class="no_webid">No Subject Alternative Names were found in your profile</p>
+  <table width="100%" rules="groups">
+      <tbody class="webid_test">
+      <tr><td colspan="2">Verification of Subject Alternative Name <span class="webid">http://bblfish.net/#me</span>?</td> </tr>
+      <tr><td class="tst_res">result</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+      <tr class="webid_cause"><td>cause</td><td class="cause_txt"></td></tr>
       </tbody>
   </table>
   </div>
 
   <h2>Certificate</h2>
 
-  <table class="cert_test" width="100%" rules="groups">
-      <caption class="tst_question">Could at least one WebID claim be verified?</caption>
-      <thead><tr><td>description</td><td class="tst_txt">At least one WebID claimed in the certificate has public key that verifies.</td></tr></thead>
-      <tbody>
-      <tr><td>subject</td><td><a href="#cert" class="tst_sub">the certificate sent by your browser</a></td></tr>
-      <tr><td>test result</td><td><font color="green" class="tst_res">passed</font></td></tr>
-      <tr><td>result description</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+  <table width="100%" rules="groups">
+      <tbody class="cert_test">
+      <tr><td colspan="2" class="tst_question">Could at least one WebID claim be verified?</td> </tr>
+      <tr><td class="tst_res">passed</td><td class="tst_res_txt">found 1 valid principals</td></tr>
       </tbody>
   </table>
 
--- a/src/main/scala/WebCache.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/WebCache.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -24,11 +24,11 @@
 package org.w3.readwriteweb
 
 import com.hp.hpl.jena.rdf.model.Model
-import java.net.URL
 import org.apache.http.MethodNotSupportedException
 import org.w3.readwriteweb.util._
 import scalaz._
 import Scalaz._
+import java.net.{ConnectException, URL}
 
 /**
  * @author Henry Story
@@ -69,7 +69,11 @@
 
         }
       })
-      http(handler)
+      try {
+         http(handler)
+      } catch {
+        case e: ConnectException => e.fail
+      }
 
     }
 
--- a/src/main/scala/auth/Principals.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/Principals.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -24,12 +24,12 @@
 package org.w3.readwriteweb.auth
 
 import java.security.Principal
-import java.net.URL
 import org.w3.readwriteweb.WebCache
 import com.hp.hpl.jena.rdf.model.Model
 import com.hp.hpl.jena.shared.WrappedIOException
 import scalaz.{Scalaz, Validation}
 import Scalaz._
+import java.net.{ConnectException, URL}
 
 /**
  * @author Henry Story from http://bblfish.net/
@@ -47,7 +47,7 @@
         val protocol = url.getProtocol
         if ("http".equals(protocol) || "https".equals(protocol)) {
           new WebID(url).success
-        } else UnsupportedProtocol("only http and https url supported at present").fail
+        } else UnsupportedProtocol("only http and https url supported at present",subjectAlternativeName).fail
     }
 
 
@@ -55,7 +55,7 @@
   def toUrl(urlStr: String): Validation[SANFailure,URL] = {
     try { new URL(urlStr).success } catch {
       // oops: should be careful, not all SANs are perhaps traditionally written out as a full URL.
-      case e => URISyntaxError("unparseable Subject Alternative Name: "+urlStr).fail
+      case e => URISyntaxError("unparseable Subject Alternative Name",urlStr).fail
     }
   }
 
@@ -77,8 +77,9 @@
 
   def getDefiningModel(implicit cache: WebCache): Validation[ProfileError, Model] =
     cache.resource(url).get() failMap {
-      case ioe: WrappedIOException => new ProfileGetError("error fetching profile", Some(ioe))
-      case other => new ProfileParseError("error parsing profile", Some(other))
+      case ioe: WrappedIOException => new ProfileGetError("error fetching profile", Some(ioe),url)
+      case connE : ConnectException => new ProfileGetError("error fetching profile", Some(connE),url)
+      case other => new ProfileParseError("error parsing profile", Some(other),url)
     }
 }
 
--- a/src/main/scala/auth/WebIdClaim.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/WebIdClaim.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -29,7 +29,9 @@
 import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
 import scalaz.{Scalaz, Validation}
 import Scalaz._
-import org.w3.readwriteweb.util.wrapValidation
+import java.net.URL
+import java.security.PublicKey
+import com.hp.hpl.jena.rdf.model.Model
 
 
 /**
@@ -52,24 +54,18 @@
 
   def hex(bytes: Array[Byte]): String = bytes.dropWhile(_ == 0).map("%02X" format _).mkString
 
-  def apply(san: String, rsakey: RSAPublicKey)(implicit cache: WebCache): Validation[WebIDClaimErr, WebIDClaim] =
-    for (id <- WebID(san) failMap {
-      case e => new WebIDClaimErr("Unsupported WebID", Some(e))
-    })
-    yield new WebIDClaim(id,rsakey)
 }
 
 /**
  * One has to construct a WebID using the object, that can do basic verifications
  */
-class WebIDClaim private (val webid: WebID, val rsakey: RSAPublicKey)(implicit cache: WebCache) {
+class WebIDClaim(val san: String, val key: PublicKey)(implicit cache: WebCache) {
+
   import WebIDClaim.hex
   import XSDDatatype._
 
-  lazy val verify: Validation[WebIDClaimErr,WebID] =
-    webid.getDefiningModel.failMap {
-      case e => new WebIDClaimErr("could not fetch model", Some(e))
-    }.flatMap { model =>
+  private def rsaTest(webid: WebID, rsakey: RSAPublicKey): (Model) => Validation[WebIDVerificationFailure, WebID] = {
+    model =>
       val initialBinding = new QuerySolutionMap();
       initialBinding.add("webid", model.createResource(webid.url.toString))
       initialBinding.add("m", model.createTypedLiteral(hex(rsakey.getModulus.toByteArray), XSDhexBinary))
@@ -77,27 +73,43 @@
       val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model, initialBinding)
       try {
         if (qe.execAsk()) webid.success
-        else new WebIDClaimErr("could not verify public key").fail
+        else new WebIDVerificationFailure("could not verify public key", None, this).fail
       } finally {
         qe.close()
       }
+  }
+
+  lazy val verify: Validation[WebIDClaimFailure, WebID] = key match {
+      case rsakey: RSAPublicKey =>
+        WebID(san).flatMap(webid=> webid.getDefiningModel.flatMap(rsaTest(webid, rsakey)) )
+      case _ => new UnsupportedKeyType("We only support RSA keys at present", key).fail
     }
-}
+  }
+
 
 trait Err {
+  type T <: AnyRef
   val msg: String
   val cause: Option[Throwable]=None
+  val subject: T
 }
 
-abstract class Failure extends Throwable with Err
+abstract class Fail extends Throwable with Err
 
-abstract class SANFailure extends Failure
-case class UnsupportedProtocol(val msg: String) extends SANFailure
-case class URISyntaxError(val msg: String) extends SANFailure
+abstract class WebIDClaimFailure extends Fail
 
-abstract class ProfileError extends Failure
-case class ProfileGetError(val msg: String,  override val cause: Option[Throwable]) extends ProfileError
-case class ProfileParseError(val msg: String, override val cause: Option[Throwable]) extends ProfileError
+class UnsupportedKeyType(val msg: String, val subject: PublicKey) extends WebIDClaimFailure { type T = PublicKey }
+
+
+abstract class SANFailure extends WebIDClaimFailure { type T = String }
+case class UnsupportedProtocol(val msg: String, subject: String) extends SANFailure
+case class URISyntaxError(val msg: String, subject: String) extends SANFailure
+
+//The subject could be more refined than the URL, especially in the paring error
+abstract class ProfileError extends WebIDClaimFailure  { type T = URL }
+case class ProfileGetError(val msg: String,  override val cause: Option[Throwable], subject: URL) extends ProfileError
+case class ProfileParseError(val msg: String, override val cause: Option[Throwable], subject: URL) extends ProfileError
 
 //it would be useful to pass the graph in
-class WebIDClaimErr(val msg: String, override val cause: Option[Throwable]=None) extends Failure
+class WebIDVerificationFailure(val msg: String, val caused: Option[Throwable], val subject: WebIDClaim)
+  extends WebIDClaimFailure { type T = WebIDClaim }
\ No newline at end of file
--- a/src/main/scala/auth/X509Claim.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/X509Claim.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -31,14 +31,13 @@
 import javax.security.auth.Refreshable
 import java.util.Date
 import collection.JavaConversions._
-import java.util.concurrent.TimeUnit
-import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
-import javax.servlet.http.HttpServletRequest
 import unfiltered.request.HttpRequest
 import java.security.interfaces.RSAPublicKey
 import collection.immutable.List
-import scalaz.{Success, Validation}
 import collection.mutable.HashMap
+import scalaz.{Scalaz, Success, Validation}
+import Scalaz._
+import java.security.PublicKey
 
 /**
  * @author hjs
@@ -110,12 +109,8 @@
   lazy val tooLate = claimReceivedDate.after(cert.getNotAfter())
   lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
 
-  /* a list of unverified principals */
-  //TODO WE ASSUME THIS IS AN RSA KEY!!
-  lazy val webidValidations: List[Validation[WebIDClaimErr, WebIDClaim]] =
-    getClaimedWebIds(cert) map { webid => WebIDClaim(webid, cert.getPublicKey.asInstanceOf[RSAPublicKey]) }
 
-  lazy val webidclaims: List[WebIDClaim] = webidValidations.collect{ case Success(webIdClaim)=> webIdClaim }
+  lazy val webidclaims: List[WebIDClaim] = getClaimedWebIds(cert) map { webid => new WebIDClaim(webid, cert.getPublicKey.asInstanceOf[RSAPublicKey]) }
 
   val verifiedClaims: List[WebID] = webidclaims.flatMap(_.verify.toOption)
 
--- a/src/main/scala/auth/X509view.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/X509view.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -77,37 +77,39 @@
 class X509Filler(x509: X509Claim)(implicit cache: WebCache) extends Transformer {
   $(".date").contents = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format( x509.claimReceivedDate)
   $(".cert_test") { node =>
-      val x509Tests = certOk.test(x509);
-      val ff = for (tst <- x509Tests) yield {
+      val x509Assertion = new Assertion(certOk,x509);
+      val x509Assertions = x509Assertion::x509Assertion.depends
+      val ff = for (ast <- x509Assertions) yield {
         new Transform(node) {
-          $(".tst_question").contents = tst.of.title
-          $(".tst_txt").contents = tst.of.description
-          $(".tst_res").contents = tst.result.name
-          $(".tst_res_txt").contents = tst.msg
+          $(".tst_question").contents = ast.test.title
+          $(".tst_txt").contents = ast.test.description
+          $(".tst_res").contents = ast.result.outcome.name
+          $(".tst_res_txt").contents = ast.result.description
         }.toNodes()
       }
       ff.flatten
   }
-//  $(".webid_tests") { node =>
-//    val ff = for (idclaim <- x509.webidclaims) yield {
-//      new Transform(node) {
-//        $(".webid").contents = "Testing webid " +idclaim.webid
-//        $(".webid_test") { n2 =>
-//          val validation: Validation[WebIDClaimErr, WebID] = idclaim.verify
-//          val nn = for (tst <-idclaim.tests) yield {
-//            new Transform(n2) {
-//              $(".tst_question").contents = tst.of.title
-//              $(".tst_txt").contents = tst.of.description
-//              $(".tst_res").contents = tst.result.name
-//              $(".tst_res_txt").contents = tst.msg
-//            }.toNodes()
-//          }
-//          nn.flatten
-//        }
-//      }.toNodes()
-//    }
-//    ff.flatten
-//  }
-//  $(".certificate").contents = x509.cert.toString
+  $(".no_webid") { node => if (x509.verifiedClaims.size==0) node else <span/> }
+  $(".webid_test") { node =>
+    val ff = for (idclaim <- x509.webidclaims) yield {
+      val idAsrt = new Assertion(webidClaimTst, idclaim)
+      new Transform(node) {
+        $(".webid").contents = idclaim.san
+        $(".tst_res_txt").contents = idAsrt.result.description
+        $(".webid_cause") { n2 =>
+          val nn = for (a <- idAsrt.depends) yield {
+            new Transform(n2) {
+              $(".cause_question").contents = a.test.title
+              $(".cause_txt").contents = a.test.description
+              $(".cause_res").contents = a.result.outcome.name
+            }.toNodes()
+          }
+          nn.flatten
+        }
+      }.toNodes()
+    }
+    ff.flatten
+  }
+  $(".certificate").contents = x509.cert.toString
 
 }
--- a/src/main/scala/auth/earl.scala	Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/earl.scala	Sat Dec 03 16:12:05 2011 +0100
@@ -24,11 +24,11 @@
 package org.w3.readwriteweb.auth
 
 import com.hp.hpl.jena.vocabulary.DCTerms
-import java.security.cert.X509Certificate
 import java.security.interfaces.RSAPublicKey
 import org.w3.readwriteweb.util.trySome
 import java.lang.ref.WeakReference
 import com.hp.hpl.jena.rdf.model.{Model, Property, ModelFactory}
+import scalaz.{Failure, Success}
 
 /**
  * Classes for the tests in WebID Authentication.
@@ -36,10 +36,10 @@
  * The idea is to try to use the earl test cases we are defining at the WebID XG as
  * a way of collecting tests done to prove the X509Claim and the WebIDClaim.
  *
- * ( bblfish: theses classes feel very ad-hoc, and it is clearly a first pass:
- *  - we are at the point currently of verifying which tests we need
- *  - one gets the feeling these classes could even contain behavior
- *  - we have instances of one class failing but not necessarily succeeding )
+ * These tests are now very close to the way the code is working. So much so that one can get
+ * feeling that perhaps by adding the URIs of these tests directly to the error codes one
+ * could merge a few classes. So perhaps with a bit of tuning a few classes here could just
+ * disappear.
  *
  */
 
@@ -72,9 +72,9 @@
 /**
  * Test with extra information taken from the ontologies
  */
-class PubTest(val name: String) extends Test {
+abstract class OntTest(val name: String) extends Test {
   import Tests._
-  implicit def boolToResult(bool: Boolean): Result = if (bool) passed else failed
+  implicit def boolToResult(bool: Boolean): Outcome = if (bool) passed else failed
 
   private def value(p: Property) = trySome(resource.getProperty(p).getLiteral.getLexicalForm) getOrElse "-missing-"
   
@@ -85,6 +85,16 @@
   private def resource = model.getResource(ns+"#"+name)
 }
 
+abstract class TestObj[T](name: String) extends OntTest(name) {
+  def apply(subj: T): Result
+  def depends(subj: T): List[Assert] =Nil
+}
+
+/**
+ * tests that we apply only to Exceptions
+ */
+class TestErr(name: String) extends OntTest(name)
+
 //todo: (bblfish:) I get the feeling that one could put the logic into the tests directly.
 //      would that make things easier or better?
 
@@ -93,67 +103,122 @@
 //
 
 //for X509Claim
-object certProvided extends PubTest("certificateProvided")   {
-  def test(cert: Option[X509Certificate]): Assertion = cert match {
-    case Some(x509) => new Assertion(this,passed,"got certificate") //the subject is the session
-    case None => new Assertion(this,failed,"missing certificate")
-  }
+//object certProvided[X509Certificate] extends OntTest("certificateProvided")   {
+//  def apply(cert: Option[X509Certificate]): Assertion = cert match {
+//    case Some(x509) => new Assertion(this,passed,"got certificate") //the subject is the session
+//    case None => new Assertion(this,failed,"missing certificate")
+//  }
+//}
+
+object certOk extends TestObj[X509Claim]("certificateOk") {
+   val parts = List[TestObj[X509Claim]](certDateOk, certProvidedSan,certPubKeyRecognized)
+
+   def apply(x509: X509Claim): Result = {
+      val deps = depends(x509)
+      val res = deps.forall(_.result.outcome == passed)
+      new Result(if (res) "X509 Certificate Good" else "X509 Certificate had problems",
+        res,  deps)
+   }
+   override def depends(x509: X509Claim): List[Assert] = parts.map(test=>new Assertion[X509Claim](test,x509))
 }
 
-object certOk extends PubTest("certificateOk") {
-   def test(x509: X509Claim): List[Assertion] = {
-     val res = certDateOk.test(x509)::certProvidedSan.test(x509)::certPubKeyRecognized.test(x509)::Nil
-     val problems = res.filter(v => v.result != passed)
-     new Assertion(this,problems.length==0,
-       if (problems.length==0) "There were no issues with the certificate"
-       else "There were some issues with your certificate")::res
-   }
-}
-object certProvidedSan extends PubTest("certificateProvidedSAN") {
-  def test(x509: X509Claim) = new Assertion(this,x509.webidclaims.size >0,
-    " There are "+x509.webidclaims.size+" SANs in the certificate")
-}
-object certDateOk extends PubTest("certificateDateOk") {
-  def test(x509: X509Claim) = 
-    new Assertion(this, x509.isCurrent,
-      "the x509certificate " + (
-        if (x509.tooEarly) "is not yet valid "
-        else if (x509.tooLate) " passed its validity date "
-        else " is valid")
-    )
-  
+object certProvidedSan extends TestObj[X509Claim]("certificateProvidedSAN") {
+  def apply(x509: X509Claim) = new Result(" There are "+x509.webidclaims.size+" SANs in the certificate",
+    x509.webidclaims.size >0)
+
 }
 
-object certPubKeyRecognized extends PubTest("certificatePubkeyRecognised") {
-  def test(claim: X509Claim) = {
+object certDateOk extends TestObj[X509Claim]("certificateDateOk") {
+  def apply(x509: X509Claim) = 
+    new Result("the x509certificate " + (
+      if (x509.tooEarly) "is not yet valid "
+      else if (x509.tooLate) " passed its validity date "
+      else " is valid"),
+      x509.isCurrent)
+
+}
+
+object certPubKeyRecognized extends TestObj[X509Claim]("certificatePubkeyRecognised") {
+  def apply(claim: X509Claim) = {
     val pk = claim.cert.getPublicKey;
-    new Assertion(this, pk.isInstanceOf[RSAPublicKey], "We only support RSA Keys at present. " )
+    new Result("We only support RSA Keys at present. ",pk.isInstanceOf[RSAPublicKey] )
   }
 }
 
 //for WebIDClaims
-object webidClaimTst extends PubTest("webidClaim")
-object pubkeyTypeTst extends PubTest("certificatePubkeyRecognised")
-object profileGetTst extends PubTest("profileGet")
-object profileParseTst extends PubTest("profileWellFormed")
-object profileOkTst extends PubTest("profileOk")
-object profileWellFormedKeyTst extends PubTest("profileWellFormedPubkey")
+object webidClaimTst extends TestObj[WebIDClaim]("webidClaim") {
+  def apply(webIdclaim: WebIDClaim) : Result =
+    webIdclaim.verify match {
+      case Success(webId) => new Result("WebId successfully verified",passed)
+      case Failure(webIdClaimErr: WebIDVerificationFailure) => 
+        new Result("keys in certificate don't match key in profile for "+webIdClaimErr.subject,failed)
+      case Failure(e) => new Result("WebID verification failed",failed,cause=List(new AssertionErr(e)))
+    }
+  
+  override def depends(webIdclaim: WebIDClaim): List[Assert] =
+    webIdclaim.verify match {
+      case Failure(e) if  !e.isInstanceOf[WebIDVerificationFailure] => new AssertionErr(e)::Nil
+      case _ => Nil
+    }
+}
 
 
-/** Assertions on the success of a Test -- sits inside X509Claim or WebIdClaim and from which full verifications can be constituted */
-class Assertion( val of: Test,
-             val result: Result = untested,
-             val msg: String,
-             val err: Option[Throwable] = None )
+
+object profileGetTst extends TestErr("profileGet")
+object profileParseTst extends TestErr("profileWellFormed")
+object sanOk extends TestErr("sanOK")
+
+//object profileOkTst extends OntTest("profileOk") {
+//  def test(err: ProfileError): Assertion = new Assertion(this,err)::err match {
+//    case getError : ProfileGetError => profileGetTst.test(getError)
+//    case ProfileParseError(parseError: Fail) => new Assertion(profileParseTst,parseError)
+//  }
+//}
+//object profileWellFormedKeyTst extends OntTest("profileWellFormedPubkey")
+
+object Assertion {
+  def name(fail: WebIDClaimFailure): Test = {
+    fail match {
+      case e: UnsupportedProtocol => sanOk
+      case e: URISyntaxError => sanOk
+      case e: ProfileGetError =>  profileGetTst
+      case e: ProfileParseError => profileParseTst
+      case e: UnsupportedKeyType => certPubKeyRecognized
+    }
+  }
+}
+
+trait Assert {
+  val subject: AnyRef
+  val test: Test
+  val result: Result
+}
+
+class Assertion[T<:AnyRef]( val test: TestObj[T], val subject: T ) extends Assert {
+  override val result: Result =  test(subject)
+  val depends: List[Assert] = test.depends(subject)
+}
+
+class AssertionErr(val fail: WebIDClaimFailure) extends Assert {
+  import Assertion.name
+  val subject = fail.subject
+  val test = name(fail)
+  val result: Result = new Result(fail.msg,failed)
+}
+
+class Result (val description: String,
+              val outcome: Outcome,
+              val cause: List[Assert] = Nil)
 
 
-sealed class Result(val name: String)  {
+
+sealed class Outcome(val name: String)  {
   val earl = "http://www.w3.org/ns/earl#"
   val id = earl+name
 }
 
-object untested extends Result("untested")
-object passed extends Result("passed")
-object failed extends Result("failed")
+object untested extends Outcome("untested")
+object passed extends Outcome("passed")
+object failed extends Outcome("failed")