seihou-okf-extension-0.8.0.0: test/Seihou/OKF/Docs/RenderSpec.hs
module Seihou.OKF.Docs.RenderSpec (tests) where
import Control.Lens ((&), (?~), (^.))
import Data.Generics.Labels ()
import Data.List (sort)
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Okf.Bundle qualified as Okf
import Okf.ConceptId qualified as Okf
import Okf.Validation (BundleValidationError (..), ValidationProfile (..))
import Seihou.Core.Migration
( BlueprintMigration (..),
EntailedEdge (..),
Migration (..),
MigrationOp (..),
)
import Seihou.Core.Types
import Seihou.OKF.Docs.Model
import Seihou.OKF.Docs.Render
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec (testSpec)
tests :: IO TestTree
tests = testSpec "Seihou.OKF.Docs.Render" spec
spec :: Spec
spec = do
describe "renderDocBundle" $ do
it "emits one concept per entry, plus the registry overview, with the documented id scheme" $ do
(concepts, problems) <- renderOrFail testOptions wellFormedModel
problems `shouldBe` []
sort (Okf.renderConceptId . Okf.conceptIdOf <$> concepts)
`shouldBe` [ "blueprints/app-blueprint",
"modules/app",
"modules/base",
"prompts/review",
"recipes/app-recipe",
"registry/fixture"
]
it "renders frontmatter fields and resource pointers" $ do
concept <- requireConcept "modules/base" testOptions wellFormedModel
let rendered = Okf.serializeConcept concept
rendered `shouldSatisfy` T.isInfixOf "type: SeihouModule"
rendered `shouldSatisfy` T.isInfixOf "title: base"
rendered `shouldSatisfy` T.isInfixOf "resource: seihou://fixture/modules/base"
rendered `shouldSatisfy` T.isInfixOf "version: 1.0.0"
it "renders resolvable cross-links to composed modules" $ do
concept <- requireConcept "recipes/app-recipe" testOptions wellFormedModel
let rendered = Okf.serializeConcept concept
rendered `shouldSatisfy` T.isInfixOf "](/modules/base.md)"
rendered `shouldSatisfy` T.isInfixOf "](/modules/app.md)"
it "validates clean for a well-formed model" $ do
(_, problems) <- renderOrFail testOptions wellFormedModel
problems `shouldBe` []
it "reports a DanglingReference for an unresolved module ref" $ do
(_, problems) <- renderOrFail testOptions danglingModel
problems `shouldSatisfy` any isDanglingReference
it "reports invalid generated concept IDs as render errors" $ do
renderDocBundle testOptions invalidIdModel
`shouldBe` Left [InvalidDocConceptId DocModuleKind "-bad" "InvalidConceptIdSegment \"-bad\""]
it "stamps a generated.by producer actor on every concept" $ do
(concepts, _) <- renderOrFail testOptions wellFormedModel
let rendered = Okf.serializeConcept <$> concepts
rendered `shouldSatisfy` all (T.isInfixOf "by: seihou-okf-extension/9.9.9")
it "omits generated.at unless the operator supplies one" $ do
concept <- requireConcept "modules/base" testOptions wellFormedModel
Okf.serializeConcept concept `shouldNotSatisfy` T.isInfixOf "at:"
it "records --generated-at verbatim in generated.at" $ do
let dated = testOptions & #generatedAt ?~ "2026-09-10"
concept <- requireConcept "modules/base" dated wellFormedModel
Okf.serializeConcept concept `shouldSatisfy` T.isInfixOf "at: 2026-09-10"
it "marks every concept stable" $ do
concept <- requireConcept "modules/base" testOptions wellFormedModel
Okf.serializeConcept concept `shouldSatisfy` T.isInfixOf "status: stable"
it "validates clean under StrictAuthoring when nothing supplies a description" $ do
(_, problems) <- renderOrFail testOptions undescribedModel
problems `shouldBe` []
it "falls back to the artifact description when the registry entry has none" $ do
concept <- requireConcept "modules/base" testOptions registrySilentModel
Okf.serializeConcept concept
`shouldSatisfy` T.isInfixOf "description: the artifact describes itself"
it "synthesizes a description when neither the registry nor the artifact has one" $ do
concept <- requireConcept "modules/base" testOptions undescribedModel
Okf.serializeConcept concept
`shouldSatisfy` T.isInfixOf "Seihou module `base` published by the `fixture` registry."
describe "artifact features" $ do
it "renders a variable declaration in full" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "- `project.name` — text, required, matching `[a-z]+`. The project name"
body `shouldSatisfy` T.isInfixOf "- `license` — one of `MIT`, `BSD-3`, optional, default `MIT`"
body `shouldSatisfy` T.isInfixOf "- `retries` — integer, optional, between 0 and 5"
it "renders an export alias" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "- `project.name` as `app.name`"
it "renders interactive prompts with choices and conditions" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Prompts"
body
`shouldSatisfy` T.isInfixOf
"- `license` — Which license? (choices: `MIT`, `BSD-3`) — when `IsSet project.name`"
it "renders generation steps with strategy, patch operation and condition" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Generation steps"
body `shouldSatisfy` T.isInfixOf "- `Template` `flake.nix.tpl` → `flake.nix`"
body
`shouldSatisfy` T.isInfixOf
"- `Copy` `gitignore.tpl` → `.gitignore` (appends one line to a file another module owns, if absent) — when `Eq license \"MIT\"`"
it "renders commands with working directory and condition" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Commands"
body `shouldSatisfy` T.isInfixOf "- `cabal build` in `app` — when `IsSet license`"
it "renders the removal procedure" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Removal"
body `shouldSatisfy` T.isInfixOf "- delete `flake.nix`"
body `shouldSatisfy` T.isInfixOf "- strip this module's section from `.gitignore`"
body `shouldSatisfy` T.isInfixOf "Then runs:"
body `shouldSatisfy` T.isInfixOf "- `rm -rf dist-newstyle`"
it "renders each module migration edge with its operations" $ do
body <- requireBody "modules/rich" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Migrations"
body `shouldSatisfy` T.isInfixOf "### 0.1.0 → 0.2.0"
body `shouldSatisfy` T.isInfixOf "- move `old.nix` → `new.nix`"
body `shouldSatisfy` T.isInfixOf "### 0.2.0 → 0.3.0"
body `shouldSatisfy` T.isInfixOf "- delete directory `legacy`"
body `shouldSatisfy` T.isInfixOf "- run `just fmt` in `."
it "omits sections the module declares nothing for" $ do
body <- requireBody "modules/base" testOptions wellFormedModel
body `shouldNotSatisfy` T.isInfixOf "## Migrations"
body `shouldNotSatisfy` T.isInfixOf "## Removal"
body `shouldNotSatisfy` T.isInfixOf "## Generation steps"
it "shows the variable bindings a recipe supplies along each edge" $ do
body <- requireBody "recipes/rich-recipe" testOptions richModel
body `shouldSatisfy` T.isInfixOf "(with `license` = `MIT`, `project.name` = `demo`)"
it "links an entailed edge whose blueprint is in the same registry" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "Entails:"
body `shouldSatisfy` T.isInfixOf "- [other-blueprint](/blueprints/other-blueprint.md) `1.9.0` → `2.0.0`"
it "labels an entailed edge whose blueprint is outside the registry" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "- `kiroku` `1.9.0` → `2.0.0` (declared outside this registry)"
it "does not turn an out-of-registry entailed edge into a dangling reference" $ do
(_, problems) <- renderOrFail testOptions richModel
problems `shouldBe` []
it "renders blueprint launch preferences and says what overrides them" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Agent launch"
body `shouldSatisfy` T.isInfixOf "- provider: `claude`"
body `shouldSatisfy` T.isInfixOf "- model: `opus`"
body `shouldSatisfy` T.isInfixOf "- effort: `high`"
body `shouldSatisfy` T.isInfixOf "SEIHOU_AGENT_*"
it "renders the version probe command and why it exists" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Version probe"
body `shouldSatisfy` T.isInfixOf "```bash\ncabal get-version\n```"
body `shouldSatisfy` T.isInfixOf "reads no package-manager format"
it "renders the blueprint tool allowlist" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Tools"
body `shouldSatisfy` T.isInfixOf "- `Bash`"
it "keeps the whole agent prompt, not just its first paragraph" $ do
body <- requireBody "blueprints/rich-blueprint" testOptions richModel
body `shouldSatisfy` T.isInfixOf "Upgrade the project."
body `shouldSatisfy` T.isInfixOf "```text\nUpgrade the project.\n\nBe careful.\n```"
it "renders command-derived variables" $ do
body <- requireBody "prompts/rich-prompt" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Command variables"
body
`shouldSatisfy` T.isInfixOf
"- `head.sha` — runs `git rev-parse HEAD` in `.`, trimmed, capped at 64 bytes — when `IsSet license`"
it "renders conditional guidance blocks" $ do
body <- requireBody "prompts/rich-prompt" testOptions richModel
body `shouldSatisfy` T.isInfixOf "## Guidance"
body `shouldSatisfy` T.isInfixOf "### Formatting"
body `shouldSatisfy` T.isInfixOf "Run the formatter before finishing."
body `shouldSatisfy` T.isInfixOf "Applies when `Eq license \"MIT\"`."
describe "registry overview concept" $ do
it "emits one concept describing the registry itself" $ do
(concepts, _) <- renderOrFail testOptions wellFormedModel
sort (Okf.renderConceptId . Okf.conceptIdOf <$> concepts)
`shouldSatisfy` elem "registry/fixture"
it "links every artifact from the registry concept, grouped by kind" $ do
body <- requireBody "registry/fixture" testOptions wellFormedModel
body `shouldSatisfy` T.isInfixOf "## Modules"
body `shouldSatisfy` T.isInfixOf "](/modules/base.md)"
body `shouldSatisfy` T.isInfixOf "## Recipes"
body `shouldSatisfy` T.isInfixOf "](/recipes/app-recipe.md)"
body `shouldSatisfy` T.isInfixOf "## Blueprints"
body `shouldSatisfy` T.isInfixOf "](/blueprints/app-blueprint.md)"
body `shouldSatisfy` T.isInfixOf "## Prompts"
body `shouldSatisfy` T.isInfixOf "](/prompts/review.md)"
it "omits a kind section the registry publishes nothing for" $ do
body <- requireBody "registry/fixture" testOptions danglingModel
body `shouldNotSatisfy` T.isInfixOf "## Recipes"
-- | A fixed producer version, so assertions on the stamped actor do not change
-- every time the package version is bumped.
testOptions :: RenderOptions
testOptions =
RenderOptions
{ producerVersion = "9.9.9",
generatedAt = Nothing,
validationProfile = StrictAuthoring,
profileSource = Nothing,
enforceProfile = True
}
-- | Render a model, failing the example rather than pattern-matching partially.
renderOrFail :: RenderOptions -> DocModel -> IO ([Okf.Concept], [BundleValidationError])
renderOrFail opts model =
case renderDocBundle opts model of
Left errs -> expectationFailure ("Expected render success, got " <> show errs) >> error "unreachable"
Right rendered -> pure rendered
-- | The rendered Markdown body of one concept, which is where every section
-- assertion above looks.
requireBody :: T.Text -> RenderOptions -> DocModel -> IO T.Text
requireBody rawId opts model = Okf.serializeConcept <$> requireConcept rawId opts model
requireConcept :: T.Text -> RenderOptions -> DocModel -> IO Okf.Concept
requireConcept rawId opts model = do
(concepts, _) <- renderOrFail opts model
case Okf.parseConceptId rawId of
Left err -> expectationFailure ("Bad test concept id: " <> show err) >> error "unreachable"
Right conceptId ->
case filter (\concept -> Okf.conceptIdOf concept == conceptId) concepts of
[concept] -> pure concept
other -> expectationFailure ("Expected one concept, got " <> show (length other)) >> error "unreachable"
isDanglingReference :: BundleValidationError -> Bool
isDanglingReference DanglingReference {} = True
isDanglingReference _ = False
wellFormedModel :: DocModel
wellFormedModel =
DocModel
{ repoName = "fixture",
repoDescription = Just "Fixture",
entries =
[ moduleEntry "base" [] "modules/base",
moduleEntry "app" [ModuleRef "base" True] "modules/app",
recipeEntry,
blueprintEntry,
promptEntry
]
}
danglingModel :: DocModel
danglingModel =
DocModel
{ repoName = "fixture",
repoDescription = Nothing,
entries =
[ moduleEntry "app" [ModuleRef "missing" False] "modules/app"
]
}
-- | Neither the registry entry nor the module says anything about itself, which
-- is what StrictAuthoring would otherwise reject.
undescribedModel :: DocModel
undescribedModel =
DocModel
{ repoName = "fixture",
repoDescription = Nothing,
entries = [silentEntry Nothing]
}
-- | The registry entry is silent but the artifact describes itself.
registrySilentModel :: DocModel
registrySilentModel =
DocModel
{ repoName = "fixture",
repoDescription = Nothing,
entries = [silentEntry (Just "the artifact describes itself")]
}
silentEntry :: Maybe T.Text -> DocEntry
silentEntry artifactDescription =
DocEntry
{ name = "base",
kind = DocModuleKind,
version = Nothing,
description = Nothing,
tags = [],
path = "modules/base",
artifact = DocModuleArtifact (silentModuleArtifact artifactDescription),
moduleRefs = [],
entailedRefs = []
}
silentModuleArtifact :: Maybe T.Text -> Module
silentModuleArtifact description =
Module
{ name = ModuleName "base",
version = Nothing,
description = description,
vars = [],
exports = [],
prompts = [],
steps = [],
commands = [],
dependencies = [],
removal = Nothing,
migrations = []
}
invalidIdModel :: DocModel
invalidIdModel =
DocModel
{ repoName = "fixture",
repoDescription = Nothing,
entries =
[ moduleEntry "-bad" [] "modules/bad"
]
}
moduleEntry :: T.Text -> [ModuleRef] -> FilePath -> DocEntry
moduleEntry name refs path =
DocEntry
{ name = name,
kind = DocModuleKind,
version = Just "1.0.0",
description = Just (name <> " module"),
tags = ["module"],
path = path,
artifact = DocModuleArtifact (moduleArtifact name refs),
moduleRefs = refs,
entailedRefs = []
}
moduleArtifact :: T.Text -> [ModuleRef] -> Module
moduleArtifact name refs =
Module
{ name = ModuleName name,
version = Just "1.0.0",
description = Just (name <> " module"),
vars = [],
exports = [],
prompts = [],
steps = [],
commands = [],
dependencies = [Dependency (ModuleName (ref ^. #name)) Map.empty | ref <- refs],
removal = Nothing,
migrations = []
}
recipeEntry :: DocEntry
recipeEntry =
DocEntry
{ name = "app-recipe",
kind = DocRecipeKind,
version = Just "0.1.0",
description = Just "Recipe",
tags = ["recipe"],
path = "recipes/app-recipe",
artifact =
DocRecipeArtifact
Recipe
{ name = RecipeName "app-recipe",
version = Just "0.1.0",
description = Just "Recipe",
modules = [simpleDep "base", simpleDep "app"],
vars = [],
prompts = []
},
moduleRefs = [ModuleRef "base" True, ModuleRef "app" True],
entailedRefs = []
}
blueprintEntry :: DocEntry
blueprintEntry =
DocEntry
{ name = "app-blueprint",
kind = DocBlueprintKind,
version = Just "0.1.0",
description = Just "Blueprint",
tags = ["blueprint"],
path = "blueprints/app-blueprint",
artifact =
DocBlueprintArtifact
Blueprint
{ name = ModuleName "app-blueprint",
version = Just "0.1.0",
description = Just "Blueprint",
prompt = "Build the app",
vars = [],
prompts = [],
baseModules = [simpleDep "base"],
files = [],
allowedTools = Nothing,
tags = ["blueprint"],
migrations = [],
launch = Nothing,
versionProbe = Nothing
},
moduleRefs = [ModuleRef "base" True],
entailedRefs = []
}
promptEntry :: DocEntry
promptEntry =
DocEntry
{ name = "review",
kind = DocPromptKind,
version = Just "0.1.0",
description = Just "Review prompt",
tags = ["prompt"],
path = "prompts/review",
artifact =
DocPromptArtifact
AgentPrompt
{ name = ModuleName "review",
version = Just "0.1.0",
description = Just "Review prompt",
prompt = "Review the change",
vars = [],
prompts = [],
commandVars = [],
files = [],
allowedTools = Nothing,
tags = ["prompt"],
launch = Nothing,
guidance = []
},
moduleRefs = [],
entailedRefs = []
}
-- | A registry whose artifacts declare every feature the renderer now covers,
-- including the four -- entailment, launch preferences, a version probe, and
-- command-derived variables -- that no real registry on this machine exercises.
richModel :: DocModel
richModel =
DocModel
{ repoName = "fixture",
repoDescription = Just "Fixture",
entries = [richModuleEntry, richRecipeEntry, richBlueprintEntry, otherBlueprintEntry, richPromptEntry]
}
richModuleEntry :: DocEntry
richModuleEntry =
DocEntry
{ name = "rich",
kind = DocModuleKind,
version = Just "0.3.0",
description = Just "A module that declares everything",
tags = ["module"],
path = "modules/rich",
artifact = DocModuleArtifact richModule,
moduleRefs = [],
entailedRefs = []
}
richModule :: Module
richModule =
Module
{ name = ModuleName "rich",
version = Just "0.3.0",
description = Just "A module that declares everything",
vars =
[ VarDecl
{ name = "project.name",
type_ = VTText,
default_ = Nothing,
description = Just "The project name",
required = True,
validation = Just (ValPattern "[a-z]+")
},
VarDecl
{ name = "license",
type_ = VTChoice ["MIT", "BSD-3"],
default_ = Just (VText "MIT"),
description = Nothing,
required = False,
validation = Nothing
},
VarDecl
{ name = "retries",
type_ = VTInt,
default_ = Nothing,
description = Nothing,
required = False,
validation = Just (ValRange 0 5)
}
],
exports = [VarExport {var = "project.name", alias = Just "app.name"}],
prompts =
[ Prompt
{ var = "license",
text = "Which license?",
condition = Just (ExprIsSet "project.name"),
choices = Just ["MIT", "BSD-3"]
}
],
steps =
[ Step
{ strategy = Template,
src = "flake.nix.tpl",
dest = "flake.nix",
condition = Nothing,
patch = Nothing
},
Step
{ strategy = Copy,
src = "gitignore.tpl",
dest = ".gitignore",
condition = Just (ExprEq "license" (VText "MIT")),
patch = Just AppendLineIfAbsent
}
],
commands =
[ Command
{ run = "cabal build",
workDir = Just "app",
condition = Just (ExprIsSet "license")
}
],
dependencies = [],
removal =
Just
Removal
{ steps =
[ RemovalStep {action = RemoveFileAction, dest = "flake.nix", src = Nothing},
RemovalStep {action = RemoveSectionAction, dest = ".gitignore", src = Nothing}
],
commands =
[Command {run = "rm -rf dist-newstyle", workDir = Nothing, condition = Nothing}]
},
migrations =
[ Migration
{ from = "0.1.0",
to = "0.2.0",
ops = [MoveFile {src = "old.nix", dest = "new.nix"}]
},
Migration
{ from = "0.2.0",
to = "0.3.0",
ops =
[ DeleteDir {path = "legacy"},
RunCommand {run = "just fmt", workDir = Just "."}
]
}
]
}
richRecipeEntry :: DocEntry
richRecipeEntry =
DocEntry
{ name = "rich-recipe",
kind = DocRecipeKind,
version = Nothing,
description = Just "A recipe that preconfigures its modules",
tags = [],
path = "recipes/rich-recipe",
artifact =
DocRecipeArtifact
Recipe
{ name = RecipeName "rich-recipe",
version = Nothing,
description = Just "A recipe that preconfigures its modules",
modules =
[ Dependency
{ module_ = ModuleName "rich",
vars = Map.fromList [("project.name", "demo"), ("license", "MIT")]
}
],
vars = [],
prompts = []
},
moduleRefs = [ModuleRef "rich" True],
entailedRefs = []
}
richBlueprintEntry :: DocEntry
richBlueprintEntry =
DocEntry
{ name = "rich-blueprint",
kind = DocBlueprintKind,
version = Nothing,
description = Just "A blueprint that declares everything",
tags = [],
path = "blueprints/rich-blueprint",
artifact = DocBlueprintArtifact richBlueprint,
moduleRefs = [],
-- One entailed edge resolves inside this registry and one does not, which
-- is the distinction the renderer has to honour.
entailedRefs =
[ EntailedRef {blueprint = "other-blueprint", from = "1.9.0", to = "2.0.0", resolved = True},
EntailedRef {blueprint = "kiroku", from = "1.9.0", to = "2.0.0", resolved = False}
]
}
richBlueprint :: Blueprint
richBlueprint =
Blueprint
{ name = ModuleName "rich-blueprint",
version = Nothing,
description = Just "A blueprint that declares everything",
prompt = "Upgrade the project.\n\nBe careful.",
vars = [],
prompts = [],
baseModules = [],
files = [],
allowedTools = Just ["Bash", "Read"],
tags = [],
migrations =
[ BlueprintMigration
{ from = "2.4.0",
to = "3.0.0",
prompt = "Cross the breaking change.",
entails =
[ EntailedEdge {blueprint = "other-blueprint", from = "1.9.0", to = "2.0.0"},
EntailedEdge {blueprint = "kiroku", from = "1.9.0", to = "2.0.0"}
]
}
],
launch =
Just
AgentLaunch
{ provider = Just "claude",
model = Just "opus",
effort = Just "high",
mode = Nothing
},
versionProbe = Just "cabal get-version"
}
-- | The blueprint the rich blueprint entails, so that one entailed edge has a
-- target inside the bundle to link to.
otherBlueprintEntry :: DocEntry
otherBlueprintEntry =
DocEntry
{ name = "other-blueprint",
kind = DocBlueprintKind,
version = Nothing,
description = Just "The entailed blueprint",
tags = [],
path = "blueprints/other-blueprint",
artifact =
DocBlueprintArtifact
Blueprint
{ name = ModuleName "other-blueprint",
version = Nothing,
description = Just "The entailed blueprint",
prompt = "Do the other thing.",
vars = [],
prompts = [],
baseModules = [],
files = [],
allowedTools = Nothing,
tags = [],
migrations = [],
launch = Nothing,
versionProbe = Nothing
},
moduleRefs = [],
entailedRefs = []
}
richPromptEntry :: DocEntry
richPromptEntry =
DocEntry
{ name = "rich-prompt",
kind = DocPromptKind,
version = Nothing,
description = Just "A prompt that declares everything",
tags = [],
path = "prompts/rich-prompt",
artifact =
DocPromptArtifact
AgentPrompt
{ name = ModuleName "rich-prompt",
version = Nothing,
description = Just "A prompt that declares everything",
prompt = "Review the change.",
vars = [],
prompts = [],
commandVars =
[ CommandVar
{ name = "head.sha",
run = "git rev-parse HEAD",
workDir = Just ".",
condition = Just (ExprIsSet "license"),
trim = True,
maxBytes = Just 64
}
],
guidance =
[ PromptGuidance
{ title = "Formatting",
body = "Run the formatter before finishing.",
condition = Just (ExprEq "license" (VText "MIT"))
}
],
files = [],
allowedTools = Nothing,
tags = [],
launch = Nothing
},
moduleRefs = [],
entailedRefs = []
}