okf-core 0.10.0.0 → 0.11.0.0
raw patch · 88 files changed
+11783/−4927 lines, 88 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Okf.Exception: catchSynchronous :: IO a -> (SomeException -> IO a) -> IO a
+ Okf.Profile: ConstraintAlternativeMissing :: ConceptId -> [FieldPath] -> Maybe FieldCondition -> ProfileViolation
+ Okf.Profile: ConstraintPresenceMissing :: ConceptId -> FieldPath -> Maybe FieldCondition -> ProfileViolation
+ Okf.Profile: DuplicateSelectedValue :: ConceptId -> FieldPath -> Text -> FieldPath -> ProfileViolation
+ Okf.Profile: FiniteDecimal :: FieldFormat
+ Okf.Profile: InvalidConceptConstraint :: Maybe Text -> Maybe Text -> ValueSelector -> Text -> ProfileDefinitionError
+ Okf.Profile: MembershipConstraint :: !ValueSelector -> !ValueSelector -> MembershipConstraint
+ Okf.Profile: MembershipValueMissing :: ConceptId -> FieldPath -> Text -> ValueSelector -> ProfileViolation
+ Okf.Profile: NestedFieldNotInProfile :: ConceptId -> FieldPath -> ProfileViolation
+ Okf.Profile: PresenceConstraint :: !Maybe FieldCondition -> ![ValueSelector] -> ![ValueSelector] -> ![ValueSelector] -> PresenceConstraint
+ Okf.Profile: TextOperandMismatch :: ConceptId -> FieldPath -> Value -> ProfileViolation
+ Okf.Profile: TimestampOrderConstraint :: !Text -> !Text -> TimestampOrderConstraint
+ Okf.Profile: TimestampOrderViolation :: ConceptId -> FieldPath -> FieldPath -> ProfileViolation
+ Okf.Profile: UniqueValuesConstraint :: [ValueSelector] -> UniqueValuesConstraint
+ Okf.Profile: ValueSelector :: !Text -> !Maybe Text -> ValueSelector
+ Okf.Profile: [after] :: TimestampOrderConstraint -> !Text
+ Okf.Profile: [before] :: TimestampOrderConstraint -> !Text
+ Okf.Profile: [member] :: ValueSelector -> !Maybe Text
+ Okf.Profile: [membershipConstraints] :: FrontmatterRules -> ![MembershipConstraint]
+ Okf.Profile: [presenceConstraints] :: NestedRules -> ![PresenceConstraint]
+ Okf.Profile: [requireAll] :: PresenceConstraint -> ![ValueSelector]
+ Okf.Profile: [requireAny] :: PresenceConstraint -> ![ValueSelector]
+ Okf.Profile: [selectors] :: UniqueValuesConstraint -> [ValueSelector]
+ Okf.Profile: [source] :: MembershipConstraint -> !ValueSelector
+ Okf.Profile: [target] :: MembershipConstraint -> !ValueSelector
+ Okf.Profile: [timestampOrderConstraints] :: NestedRules -> ![TimestampOrderConstraint]
+ Okf.Profile: [uniqueValuesConstraints] :: FrontmatterRules -> ![UniqueValuesConstraint]
+ Okf.Profile: [whenAnyPresent] :: PresenceConstraint -> ![ValueSelector]
+ Okf.Profile: compiledProfileBaseConstraints :: CompiledProfile -> EffectiveConceptConstraints
+ Okf.Profile: compiledProfileConstraintsForType :: CompiledProfile -> Text -> EffectiveConceptConstraints
+ Okf.Profile: conceptMembershipConstraints :: EffectiveConceptConstraints -> [MembershipConstraint]
+ Okf.Profile: conceptPresenceConstraints :: EffectiveConceptConstraints -> [PresenceConstraint]
+ Okf.Profile: conceptResolvedSelectors :: EffectiveConceptConstraints -> Map ValueSelector EffectiveFieldRule
+ Okf.Profile: conceptTimestampOrderConstraints :: EffectiveConceptConstraints -> [TimestampOrderConstraint]
+ Okf.Profile: conceptUniqueValuesConstraints :: EffectiveConceptConstraints -> [UniqueValuesConstraint]
+ Okf.Profile: data EffectiveConceptConstraints
+ Okf.Profile: data MembershipConstraint
+ Okf.Profile: data PresenceConstraint
+ Okf.Profile: data TimestampOrderConstraint
+ Okf.Profile: data ValueSelector
+ Okf.Profile: fieldRuleElementAllowsUnknownFields :: EffectiveFieldRule -> Maybe Bool
+ Okf.Profile: fieldRuleElementConstraints :: EffectiveFieldRule -> EffectiveConceptConstraints
+ Okf.Profile: fieldRuleObjectAllowsUnknownFields :: EffectiveFieldRule -> Maybe Bool
+ Okf.Profile: fieldRuleObjectConstraints :: EffectiveFieldRule -> EffectiveConceptConstraints
+ Okf.Profile: frontmatterConstraints :: FrontmatterRules -> EffectiveConceptConstraints
+ Okf.Profile: instance Data.Aeson.Types.ToJSON.ToJSON Okf.Profile.MembershipConstraint
+ Okf.Profile: instance Data.Aeson.Types.ToJSON.ToJSON Okf.Profile.PresenceConstraint
+ Okf.Profile: instance Data.Aeson.Types.ToJSON.ToJSON Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance Data.Aeson.Types.ToJSON.ToJSON Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance Data.Aeson.Types.ToJSON.ToJSON Okf.Profile.ValueSelector
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.MembershipConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureCardinality
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureFieldCondition
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureFieldFormat
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureFieldRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureFrontmatterRules
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureHandleReferenceRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureMembershipConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureNestedFieldRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureNestedRules
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosurePathReferenceRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosurePresenceConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureProfileSpec
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureTimestampOrderConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureTypeRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureUniqueValuesConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreNestedClosureValueSelector
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryFieldRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryFrontmatterRules
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryNestedFieldRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryNestedRules
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryProfileSpec
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PreProductDiscoveryTypeRule
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.PresenceConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance Dhall.Marshal.Decode.FromDhall Okf.Profile.ValueSelector
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.EffectiveConceptConstraints
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.MembershipConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureCardinality
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureFieldCondition
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureFieldFormat
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureFieldRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureFrontmatterRules
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureHandleReferenceRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureMembershipConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureNestedFieldRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureNestedRules
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosurePathReferenceRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosurePresenceConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureProfileSpec
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureTimestampOrderConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureTypeRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureUniqueValuesConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreNestedClosureValueSelector
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryFieldRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryFrontmatterRules
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryNestedFieldRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryNestedRules
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryProfileSpec
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PreProductDiscoveryTypeRule
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.PresenceConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance GHC.Classes.Eq Okf.Profile.ValueSelector
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.MembershipConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureCardinality
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureFieldCondition
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureFieldFormat
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureHandleReferenceRule
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureMembershipConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosurePathReferenceRule
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosurePresenceConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureTimestampOrderConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureUniqueValuesConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PreNestedClosureValueSelector
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.PresenceConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance GHC.Classes.Ord Okf.Profile.ValueSelector
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.EffectiveConceptConstraints
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.MembershipConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureCardinality
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureFieldCondition
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureFieldFormat
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureFieldRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureFrontmatterRules
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureHandleReferenceRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureMembershipConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureNestedFieldRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureNestedRules
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosurePathReferenceRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosurePresenceConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureProfileSpec
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureTimestampOrderConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureTypeRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureUniqueValuesConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreNestedClosureValueSelector
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryFieldRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryFrontmatterRules
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryNestedFieldRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryNestedRules
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryProfileSpec
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PreProductDiscoveryTypeRule
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.PresenceConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance GHC.Internal.Generics.Generic Okf.Profile.ValueSelector
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.EffectiveConceptConstraints
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.MembershipConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureCardinality
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureFieldCondition
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureFieldFormat
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureFieldRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureFrontmatterRules
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureHandleReferenceRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureMembershipConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureNestedFieldRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureNestedRules
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosurePathReferenceRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosurePresenceConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureProfileSpec
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureTimestampOrderConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureTypeRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureUniqueValuesConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreNestedClosureValueSelector
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryFieldRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryFrontmatterRules
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryNestedFieldRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryNestedRules
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryProfileSpec
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PreProductDiscoveryTypeRule
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.PresenceConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.TimestampOrderConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.UniqueValuesConstraint
+ Okf.Profile: instance GHC.Internal.Show.Show Okf.Profile.ValueSelector
+ Okf.Profile: nestedConstraints :: NestedRules -> EffectiveConceptConstraints
+ Okf.Profile: newtype UniqueValuesConstraint
+ Okf.Profile: renderConceptConstraints :: EffectiveConceptConstraints -> [Text]
+ Okf.Profile: renderValueSelector :: ValueSelector -> Text
+ Okf.Profile: resolveValueSelector :: Map Text EffectiveFieldRule -> ValueSelector -> Maybe EffectiveFieldRule
- Okf.Profile: FrontmatterRules :: ![FieldRule] -> ![FieldRule] -> ![FieldRule] -> FrontmatterRules
+ Okf.Profile: FrontmatterRules :: ![FieldRule] -> ![FieldRule] -> ![FieldRule] -> ![PresenceConstraint] -> ![TimestampOrderConstraint] -> ![MembershipConstraint] -> ![UniqueValuesConstraint] -> FrontmatterRules
- Okf.Profile: NestedRules :: ![NestedFieldRule] -> ![NestedFieldRule] -> ![NestedFieldRule] -> NestedRules
+ Okf.Profile: NestedRules :: !Bool -> ![NestedFieldRule] -> ![NestedFieldRule] -> ![NestedFieldRule] -> ![PresenceConstraint] -> ![TimestampOrderConstraint] -> NestedRules
- Okf.Profile: [allowUnknownFields] :: ProfileSpec -> !Bool
+ Okf.Profile: [allowUnknownFields] :: NestedRules -> !Bool
Files
- CHANGELOG.md +34/−0
- dhall/FieldFormat.dhall +1/−0
- dhall/FrontmatterRules.dhall +4/−0
- dhall/MembershipConstraint.dhall +1/−0
- dhall/NestedRules.dhall +7/−1
- dhall/PresenceConstraint.dhall +5/−0
- dhall/TimestampOrderConstraint.dhall +1/−0
- dhall/UniqueValuesConstraint.dhall +1/−0
- dhall/ValueSelector.dhall +1/−0
- dhall/defaults/FrontmatterRules.dhall +4/−0
- dhall/defaults/MembershipConstraint.dhall +5/−0
- dhall/defaults/NestedRules.dhall +4/−1
- dhall/defaults/PresenceConstraint.dhall +8/−0
- dhall/defaults/TimestampOrderConstraint.dhall +3/−0
- dhall/defaults/UniqueValuesConstraint.dhall +3/−0
- dhall/defaults/ValueSelector.dhall +1/−0
- dhall/package.dhall +13/−2
- okf-core.cabal +4/−3
- src/Okf/Exception.hs +11/−0
- src/Okf/Profile.hs +5648/−4638
- src/Okf/Profile/Discovery.hs +4/−3
- src/Okf/Profile/Documentation.hs +11/−1
- src/Okf/Profile/Registry.hs +5/−4
- src/Okf/Query.hs +1/−0
- test/Main.hs +663/−79
- test/fixtures/catalogue/Profile/Hypothesis.dhall +43/−0
- test/fixtures/catalogue/Profile/V02.dhall +30/−3
- test/fixtures/catalogue/package.dhall +5/−4
- test/fixtures/catalogue/profiles/assurance/claim-checks.dhall +183/−0
- test/fixtures/catalogue/profiles/assurance/package.dhall +3/−1
- test/fixtures/catalogue/profiles/coordination/audiences.dhall +305/−0
- test/fixtures/catalogue/profiles/coordination/campaigns.dhall +466/−0
- test/fixtures/catalogue/profiles/coordination/capabilities.dhall +6/−2
- test/fixtures/catalogue/profiles/coordination/designs-for.dhall +610/−0
- test/fixtures/catalogue/profiles/coordination/designs.dhall +5/−0
- test/fixtures/catalogue/profiles/coordination/features.dhall +300/−0
- test/fixtures/catalogue/profiles/coordination/friction-log.dhall +293/−0
- test/fixtures/catalogue/profiles/coordination/jobs.dhall +287/−0
- test/fixtures/catalogue/profiles/coordination/launches.dhall +292/−0
- test/fixtures/catalogue/profiles/coordination/package.dhall +32/−1
- test/fixtures/catalogue/profiles/coordination/strategy.dhall +440/−0
- test/fixtures/catalogue/profiles/coordination/transitions.dhall +349/−0
- test/fixtures/catalogue/profiles/coordination/use-cases.dhall +70/−2
- test/fixtures/catalogue/profiles/documentation/architecture-decisions.dhall +39/−0
- test/fixtures/catalogue/profiles/documentation/messaging.dhall +275/−0
- test/fixtures/catalogue/profiles/documentation/package.dhall +5/−1
- test/fixtures/catalogue/profiles/documentation/product-research.dhall +336/−0
- test/fixtures/catalogue/profiles/documentation/runbooks.dhall +121/−0
- test/fixtures/profile-discovery/dist-newstyle/valid-build-output.dhall +0/−2
- test/fixtures/profile-nested-closure-invalid/index.md +6/−0
- test/fixtures/profile-nested-closure-invalid/log.md +4/−0
- test/fixtures/profile-nested-closure-invalid/note.md +10/−0
- test/fixtures/profile-nested-closure-valid/index.md +6/−0
- test/fixtures/profile-nested-closure-valid/log.md +4/−0
- test/fixtures/profile-nested-closure-valid/note.md +10/−0
- test/fixtures/profile-product-discovery-invalid/index.md +6/−0
- test/fixtures/profile-product-discovery-invalid/job.md +25/−0
- test/fixtures/profile-product-discovery-invalid/log.md +4/−0
- test/fixtures/profile-product-discovery-valid/index.md +6/−0
- test/fixtures/profile-product-discovery-valid/job.md +26/−0
- test/fixtures/profile-product-discovery-valid/log.md +4/−0
- test/fixtures/profiles/attested-computation-house.dhall +6/−37
- test/fixtures/profiles/cardinality.dhall +6/−4
- test/fixtures/profiles/closed-fields.dhall +10/−7
- test/fixtures/profiles/concept-filters.dhall +8/−19
- test/fixtures/profiles/conditional-fields-invalid.dhall +4/−2
- test/fixtures/profiles/conditional-fields.dhall +16/−10
- test/fixtures/profiles/decisions.dhall +6/−8
- test/fixtures/profiles/document-references-invalid.dhall +6/−6
- test/fixtures/profiles/document-references.dhall +9/−6
- test/fixtures/profiles/formats.dhall +6/−4
- test/fixtures/profiles/guidance.dhall +4/−5
- test/fixtures/profiles/nested-closure-malformed-type.dhall +18/−0
- test/fixtures/profiles/nested-closure-malformed.dhall +13/−0
- test/fixtures/profiles/nested-closure-registry.dhall +1/−0
- test/fixtures/profiles/nested-closure.dhall +43/−0
- test/fixtures/profiles/nested-references-and-uniqueness.dhall +12/−5
- test/fixtures/profiles/nested-reviews.dhall +23/−7
- test/fixtures/profiles/optional-collision-invalid.dhall +6/−9
- test/fixtures/profiles/optional-conditional-invalid.dhall +10/−11
- test/fixtures/profiles/optional-fields.dhall +12/−19
- test/fixtures/profiles/postgresql.dhall +10/−14
- test/fixtures/profiles/pre-nested-closure.dhall +182/−0
- test/fixtures/profiles/pre-product-discovery-0.10.0.0.dhall +177/−0
- test/fixtures/profiles/product-discovery-malformed.dhall +2/−0
- test/fixtures/profiles/product-discovery-registry.dhall +1/−0
- test/fixtures/profiles/product-discovery.dhall +131/−0
- test/fixtures/profiles/type-frontmatter.dhall +12/−6
CHANGELOG.md view
@@ -7,6 +7,40 @@ ## [Unreleased] +## [0.11.0.0] - 2026-10-28++### Added++- Bounded `ValueSelector` declarations, grouped presence, timestamp ordering,+ text membership, combined uniqueness, and `FiniteDecimal` validation. Effective+ root and nested constraints have read-only inspection APIs; generated profile+ documentation renders them. Frozen compatibility decoders preserve complete+ historical descriptor generations in file and registry workflows.+- Opt-in `NestedRules.allowUnknownFields` closure for list records and object+ values, defaulting to open. Names merge by union and policies with AND across+ scopes; diagnostics use indexed/member paths in both validation modes. Raw+ inspection exposes declared booleans and generated documentation shows+ effective open/closed/none policies. Older descriptors retain open records.++### Changed++- Requires GHC >=9.12 (base >=4.21). Profile/configuration exception handling+ preserves asynchronous cancellation.+- **Breaking (library/schema):** `FrontmatterRules` gains four constraint lists;+ `NestedRules` gains two constraint lists and `allowUnknownFields`; `FieldFormat`+ gains `FiniteDecimal`. Use record completion for new defaulted fields or retain+ a frozen older schema import for existing descriptors.+- **Breaking (library):** New error/violation constructors are+ `InvalidConceptConstraint`, `ConstraintPresenceMissing`,+ `ConstraintAlternativeMissing`, `TimestampOrderViolation`, `TextOperandMismatch`,+ `MembershipValueMissing`, `DuplicateSelectedValue`, and+ `NestedFieldNotInProfile ConceptId FieldPath`. Exhaustive consumers must handle+ them, including `mori://shinzui/mori/packages/mori-cli`.+- `defaultRegistryReference` now pins `mori://shinzui/okf-profiles` v0.25.0+ (from v0.19.0), with thirty profiles verified by the offline catalogue fixture.+ New exports cover transitions, runbooks, product discovery, designs, friction+ logs, campaigns, audiences, claim checks, strategy, launches, and messaging.+ ## [0.10.0.0] - 2026-10-06 ### Added
dhall/FieldFormat.dhall view
@@ -15,5 +15,6 @@ | HumanActor | Integer | NonNegativeInteger+| FiniteDecimal | Boolean >
dhall/FrontmatterRules.dhall view
@@ -19,4 +19,8 @@ in { required : List FieldRule , recommended : List FieldRule , optional : List FieldRule+ , presenceConstraints : List ./PresenceConstraint.dhall+ , timestampOrderConstraints : List ./TimestampOrderConstraint.dhall+ , membershipConstraints : List ./MembershipConstraint.dhall+ , uniqueValuesConstraints : List ./UniqueValuesConstraint.dhall }
+ dhall/MembershipConstraint.dhall view
@@ -0,0 +1,1 @@+{ source : ./ValueSelector.dhall, target : ./ValueSelector.dhall }
dhall/NestedRules.dhall view
@@ -5,9 +5,15 @@ -- always checked, `recommended` only under `--strict`, and an `optional` member -- is never reported when absent while its declared constraints still apply -- whenever it is present.+-- Set allowUnknownFields to False to reject every supplied undeclared member.+-- All three declaration lists contribute names even when a presence condition+-- is false. Matching profile/type policies combine with AND. let NestedFieldRule = ./NestedFieldRule.dhall -in { required : List NestedFieldRule+in { allowUnknownFields : Bool+ , required : List NestedFieldRule , recommended : List NestedFieldRule , optional : List NestedFieldRule+ , presenceConstraints : List ./PresenceConstraint.dhall+ , timestampOrderConstraints : List ./TimestampOrderConstraint.dhall }
+ dhall/PresenceConstraint.dhall view
@@ -0,0 +1,5 @@+{ when : Optional ./FieldCondition.dhall+, whenAnyPresent : List ./ValueSelector.dhall+, requireAll : List ./ValueSelector.dhall+, requireAny : List ./ValueSelector.dhall+}
+ dhall/TimestampOrderConstraint.dhall view
@@ -0,0 +1,1 @@+{ before : Text, after : Text }
+ dhall/UniqueValuesConstraint.dhall view
@@ -0,0 +1,1 @@+{ selectors : List ./ValueSelector.dhall }
+ dhall/ValueSelector.dhall view
@@ -0,0 +1,1 @@+{ field : Text, member : Optional Text }
dhall/defaults/FrontmatterRules.dhall view
@@ -8,5 +8,9 @@ { required = [] : List FieldRule , recommended = [] : List FieldRule , optional = [] : List FieldRule+ , presenceConstraints = [] : List ../PresenceConstraint.dhall+ , timestampOrderConstraints = [] : List ../TimestampOrderConstraint.dhall+ , membershipConstraints = [] : List ../MembershipConstraint.dhall+ , uniqueValuesConstraints = [] : List ../UniqueValuesConstraint.dhall } }
+ dhall/defaults/MembershipConstraint.dhall view
@@ -0,0 +1,5 @@+let Selector = ./ValueSelector.dhall++in { Type = ../MembershipConstraint.dhall+ , default = { source = Selector::{=}, target = Selector::{=} }+ }
dhall/defaults/NestedRules.dhall view
@@ -6,8 +6,11 @@ in { Type = NestedRulesType , default =- { required = [] : List NestedFieldRule+ { allowUnknownFields = True+ , required = [] : List NestedFieldRule , recommended = [] : List NestedFieldRule , optional = [] : List NestedFieldRule+ , presenceConstraints = [] : List ../PresenceConstraint.dhall+ , timestampOrderConstraints = [] : List ../TimestampOrderConstraint.dhall } }
+ dhall/defaults/PresenceConstraint.dhall view
@@ -0,0 +1,8 @@+{ Type = ../PresenceConstraint.dhall+, default =+ { when = None ../FieldCondition.dhall+ , whenAnyPresent = [] : List ../ValueSelector.dhall+ , requireAll = [] : List ../ValueSelector.dhall+ , requireAny = [] : List ../ValueSelector.dhall+ }+}
+ dhall/defaults/TimestampOrderConstraint.dhall view
@@ -0,0 +1,3 @@+{ Type = ../TimestampOrderConstraint.dhall+, default = { before = "", after = "" }+}
+ dhall/defaults/UniqueValuesConstraint.dhall view
@@ -0,0 +1,3 @@+{ Type = ../UniqueValuesConstraint.dhall+, default.selectors = [] : List ../ValueSelector.dhall+}
+ dhall/defaults/ValueSelector.dhall view
@@ -0,0 +1,1 @@+{ Type = ../ValueSelector.dhall, default = { field = "", member = None Text } }
dhall/package.dhall view
@@ -12,7 +12,8 @@ -- `defaults` holds the `{ Type, default }` record-completion modules; `mk` holds -- constructor functions for the types authors write repeatedly. Both protect a -- descriptor written from now on against future additive, defaulted schema--- fields; neither is a compatibility mechanism for descriptors that already+-- fields (including the open-by-default NestedRules.allowUnknownFields policy);+-- neither is a compatibility mechanism for descriptors that already -- exist (okf-core's legacy fallback decoder is what keeps those loading). { Profile = ./Profile.dhall , TypeRule = ./TypeRule.dhall@@ -25,8 +26,18 @@ , NestedFieldRule = ./NestedFieldRule.dhall , Cardinality = ./Cardinality.dhall , FieldFormat = ./FieldFormat.dhall+, ValueSelector = ./ValueSelector.dhall+, PresenceConstraint = ./PresenceConstraint.dhall+, TimestampOrderConstraint = ./TimestampOrderConstraint.dhall+, MembershipConstraint = ./MembershipConstraint.dhall+, UniqueValuesConstraint = ./UniqueValuesConstraint.dhall , defaults =- { Profile = ./defaults/Profile.dhall+ { ValueSelector = ./defaults/ValueSelector.dhall+ , PresenceConstraint = ./defaults/PresenceConstraint.dhall+ , TimestampOrderConstraint = ./defaults/TimestampOrderConstraint.dhall+ , MembershipConstraint = ./defaults/MembershipConstraint.dhall+ , UniqueValuesConstraint = ./defaults/UniqueValuesConstraint.dhall+ , Profile = ./defaults/Profile.dhall , TypeRule = ./defaults/TypeRule.dhall , FrontmatterRules = ./defaults/FrontmatterRules.dhall , FieldRule = ./defaults/FieldRule.dhall
okf-core.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: okf-core-version: 0.10.0.0+version: 0.11.0.0 synopsis: Read, validate, index, and traverse Open Knowledge Format bundles @@ -61,6 +61,7 @@ Okf.ConceptId Okf.Discovery Okf.Document+ Okf.Exception Okf.Graph Okf.Index Okf.Log@@ -79,7 +80,7 @@ build-depends: aeson >=2.2 && <2.4, attoparsec >=0.14 && <0.15,- base >=4.20 && <5,+ base >=4.21 && <5, bytestring >=0.11 && <0.13, cmark-gfm ^>=0.2, containers >=0.6 && <0.8,@@ -104,7 +105,7 @@ hs-source-dirs: test build-depends: aeson,- base >=4.20 && <5,+ base >=4.21 && <5, containers >=0.6 && <0.8, dhall >=1.41 && <1.43, directory,
+ src/Okf/Exception.hs view
@@ -0,0 +1,11 @@+-- | Catch synchronous failures while preserving asynchronous cancellation.+module Okf.Exception (catchSynchronous) where++import Control.Exception (SomeAsyncException, SomeException, catch, fromException, throwIO)++catchSynchronous :: IO a -> (SomeException -> IO a) -> IO a+catchSynchronous action handler =+ action `catch` \exception ->+ case fromException exception :: Maybe SomeAsyncException of+ Just _ -> throwIO exception+ Nothing -> handler exception
src/Okf/Profile.hs view
@@ -12,4641 +12,5651 @@ module Okf.Profile ( -- * Descriptor ProfileSpec (..),- FrontmatterRules (..),- FieldCondition (..),- HandleReferenceRule (..),- PathReferenceRule (..),- FieldRule (..),- NestedRules (..),- NestedFieldRule (..),- Cardinality (..),- FieldFormat (..),- TypeRule (..),- FieldPath (..),- FieldPathSegment (..),- loadProfileFile,- decodeProfileExpr,- profileFieldDescription,- CompiledProfile,- ProfileDefinitionError (..),- compileProfile,- compiledProfileSpec,- compiledProfileRequiredBundleVersion,- profileFieldDescriptionForType,-- -- * Compiled rule inspection-- -- | A read-only window onto what a compiled profile actually demands. The- -- rule types are abstract on purpose: read them through the accessors below- -- so that later profile features can extend the compiled encoding without- -- breaking consumers.- --- -- Two encodings are easy to misread and are worth stating up front. An- -- __empty presence-clause list means the key is optional__, not that it is- -- unconstrained; and an __empty allowed-value list means unconstrained__,- -- not that no value is permitted.- EffectiveFieldRule,- PresenceClause,- FieldRequirement (..),- fieldRulePresenceClauses,- presenceClauseRequirement,- presenceClauseCondition,- fieldRuleDescription,- fieldRuleAllowedValues,- fieldRuleCardinality,- fieldRuleFormat,- fieldRuleReference,- fieldRuleUniqueBy,- fieldRulePath,- fieldRuleElementFields,- fieldRuleObjectFields,- compiledProfileTypeNames,- compiledProfileBaseRules,- compiledProfileRulesForType,- renderCardinalityName,- renderFieldFormatName,-- -- * Validation- DocumentId (..),- parseDocumentId,- renderDocumentId,- documentIdsInBundle,- nextDocumentId,- ProfileViolation (..),- validateProfile,- validateProfileWith,- validateProfileVersion,-- -- * Body inspection- schemaSectionColumns,- )-where--import CMarkGFM qualified-import Control.Exception (SomeException, catch)-import Data.Aeson (ToJSON (..), object, (.=))-import Data.Aeson qualified as Aeson-import Data.Aeson.Key qualified as Aeson.Key-import Data.Aeson.KeyMap qualified as Aeson.KeyMap-import Data.Char (isAsciiLower, isAsciiUpper)-import Data.List qualified as List-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Data.Maybe (catMaybes, mapMaybe)-import Data.Set qualified as Set-import Data.Text qualified as Text-import Data.Text.Read qualified as Text.Read-import Data.Time.Calendar (Day)-import Data.Time.Clock (UTCTime)-import Data.Time.Format.ISO8601 (iso8601ParseM)-import Data.Vector qualified as Vector-import Data.Void (Void)-import Dhall (FromDhall (..), auto, genericAutoWith)-import Dhall qualified-import Dhall.Core (Expr)-import Dhall.Core qualified as Dhall.Core-import Dhall.Map qualified as Dhall.Map-import Dhall.Src (Src)-import Network.URI (parseURI, uriScheme)-import Numeric.Natural (Natural)--- Imported qualified: 'Okf.Actor.HumanActor' is a constructor of 'Actor.Actor'--- and 'HumanActor' is a constructor of 'FieldFormat', so an unqualified import--- makes the two ambiguous.-import Okf.Actor qualified as Actor-import Okf.Bundle- ( BundleInventory,- Concept,- bundleInventoryMember,- bundleInventoryOfConcepts,- conceptDocument,- conceptIdOf,- conceptResource,- conceptType,- )-import Okf.ConceptId (ConceptId, conceptIdFromFilePath, renderConceptId)-import Okf.Document- ( Frontmatter,- coreFrontmatterFields,- fieldsSupersededInV02,- frontmatterKeys,- frontmatterLookup,- )-import Okf.Index- ( OkfVersion (..),- VersionDeclaration (..),- parseOkfVersion,- renderOkfVersion,- supportedOkfVersion,- )-import Okf.Markdown (markdownOptions)-import Okf.Path (PathReference (..), classifyPathReference)--- 'List' and 'Object' are 'Cardinality' constructors here; the names aeson uses--- for the corresponding 'Value' constructors are reached as 'Aeson.Array' and--- 'Aeson.Object'.-import Okf.Prelude hiding (List, Object, (.=))-import Okf.Validation (ValidationProfile (..))-import System.FilePath qualified as FilePath-import Text.Regex.TDFA (Regex, defaultCompOpt, defaultExecOpt)-import Text.Regex.TDFA.Text qualified as Regex.Text-import "generic-lens" Data.Generics.Labels ()---- | A complete house profile. @description@ concisely documents the profile's--- identity. @guidance@ is multiline Markdown prescribing how authors should do--- the work. Neither is checked against a bundle or executed, and neither can--- produce a 'ProfileViolation'.------ @requireBundleVersion@ and @okfVersion@ are easy to confuse and answer--- different questions. @okfVersion@ says which version's rules /this profile--- writes/, and 'compileProfile' checks the profile's own rules against it.--- @requireBundleVersion@ says what the profile demands of a /bundle/: @Just--- \"0.2\"@ means the bundle's root @index.md@ must declare @okf_version@ at 0.2--- or later, checked by 'validateProfileVersion'. Neither constrains the other.-data ProfileSpec = ProfileSpec- { name :: !Text,- description :: !(Maybe Text),- guidance :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !FrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- requireBundleVersion :: !(Maybe Text),- types :: ![TypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | Frontmatter keys the profile expects on every concept. @required@ is always--- checked, @recommended@ only under 'StrictAuthoring', and @optional@ never:--- an optional key is fully validated whenever it is present and its absence is--- never a deviation in any mode.-data FrontmatterRules = FrontmatterRules- { required :: ![FieldRule],- recommended :: ![FieldRule],- optional :: ![FieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | A same-scope predicate controlling whether a field's presence rule applies.--- The source field must compile to a closed scalar textual vocabulary.-data FieldCondition = FieldCondition- { field :: !Text,- hasValue :: ![Text]- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)---- | A top-level field whose textual values point either to a local document--- handle with one prefix or to an absolute URI with an explicitly allowed--- scheme. External targets are never resolved by okf.-data HandleReferenceRule = HandleReferenceRule- { localPrefix :: !Text,- externalUriSchemes :: ![Text],- allowSelf :: !Bool,- allowLocal :: !Bool,- externalUriPattern :: !(Maybe Text)- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)---- | A field whose values name a path or URI per OKF v0.2 specification §6.2: an--- absolute URL, a bundle-relative path beginning with @\/@, or an ordinary--- relative path resolved against the concept's own directory.------ Deliberately distinct from 'HandleReferenceRule'. A handle resolves against--- the bundle's document-ID index and fails by carrying the wrong prefix or--- having no owner; a path resolves against the concept tree and fails by not--- being a §6.2 shape, by climbing above the bundle root, or by naming a concept--- that is not there. Declaring both on one rule is a definition error.------ An empty @externalUriSchemes@ means no absolute URL is permitted at all, so a--- separate "must be a path, never a URL" flag would say nothing new. Which--- bundle paths okf can resolve depends on the entry point: 'validateProfileWith'--- resolves every file the bundle holds, including §6.3's--- @references\/attesters\/revenue.py@, while 'validateProfile' is handed--- concepts alone and so resolves @.md@ targets only.-data PathReferenceRule = PathReferenceRule- { externalUriSchemes :: ![Text],- allowSelf :: !Bool- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)---- | One documented frontmatter key. The description is prose for humans and is--- never checked against a bundle.------ @elementFields@ and @objectFields@ describe two different shapes.--- @elementFields@ constrains the record inside /each element of a list/;--- @objectFields@ constrains the record that /is/ the value. Declaring both means--- either spelling is accepted and both are checked against the same member--- rules, which is how a profile describes the OKF v0.2 @verified@ key.-data FieldRule = FieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- elementFields :: !(Maybe NestedRules),- objectFields :: !(Maybe NestedRules),- reference :: !(Maybe HandleReferenceRule),- path :: !(Maybe PathReferenceRule),- when :: !(Maybe FieldCondition),- uniqueBy :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | Rules for the flat object stored in each element of a list-valued field, or--- in the mapping that is the value of an object-valued field. The nested rule--- type has neither an @elementFields@ nor an @objectFields@ member, which makes--- the public descriptor depth-bounded rather than recursive.-data NestedRules = NestedRules- { required :: ![NestedFieldRule],- recommended :: ![NestedFieldRule],- optional :: ![NestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | One field inside a list element record or an object-valued mapping. It--- carries @path@ because @sources[].resource@ is the motivating path-valued--- field of §6.2 and lives inside a list element; it deliberately carries no--- @reference@, because no v0.2 field names a document handle at nested scope.-data NestedFieldRule = NestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- path :: !(Maybe PathReferenceRule),- when :: !(Maybe FieldCondition),- reference :: !(Maybe HandleReferenceRule)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | Whether a key must be a single value, a non-empty list, a mapping, or any--- of them. 'Object' is placed last so the derived 'Ord' keeps the relative--- order of the three published alternatives, which the definition-error sort key--- relies on.-data Cardinality = Any | Scalar | List | Object- deriving stock (Generic, Eq, Ord, Show)---- | Decoded from the three-alternative published union in--- @okf-core\/dhall\/Cardinality.dhall@. 'Object' is deliberately unreachable--- from Dhall: it is produced only by compilation, when a rule declares--- @objectFields@. Adding an alternative to the published union would change the--- type of every value written against it and would break descriptors pinned to--- the previous schema, which no record-level fallback decoder can repair,--- because every frozen generation in the chain refers to this same type. An--- author who wants to say "this key must be a mapping" and nothing more writes--- @objectFields = Some NestedRules::{=}@.-instance FromDhall Cardinality where- autoWith _normalizer =- Dhall.union- ( (Any <$ Dhall.constructor "Any" Dhall.unit)- <> (Scalar <$ Dhall.constructor "Scalar" Dhall.unit)- <> (List <$ Dhall.constructor "List" Dhall.unit)- )---- | A named value format. Formats constrain present values but do not imply--- that a field must be present.------ The first five constrain text. 'Actor' and 'HumanActor' constrain text against--- the OKF v0.2 actor convention of specification §7, classified by--- 'Okf.Actor.parseActor'. 'Integer', 'NonNegativeInteger', and 'Boolean'--- constrain a value that is not text at all; declaring one of them refines an--- unspecified cardinality to 'Scalar', because otherwise a numeric or boolean--- key is reported missing before its value is ever examined.------ The OKF v0.2 alternatives are appended after 'DocumentHandle' so the derived--- 'Ord' keeps the relative order of the original five, which the--- definition-error sort key relies on.-data FieldFormat- = Rfc3339Utc- | Date- | Uri- | UriWithScheme Text- | DocumentHandle Text- | Actor- | HumanActor- | Integer- | NonNegativeInteger- | Boolean- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)---- | One rule per allowed concept @type@ string. @guidance@ adds authoring--- procedure specific to this type; it is documentary and never executed.-data TypeRule = TypeRule- { type_ :: !Text,- description :: !(Maybe Text),- guidance :: !(Maybe Text),- frontmatter :: !FrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)---- | Decode @type_@ from the Dhall field @type@ by stripping the trailing--- underscore; all other fields map by their exact name. (Mirrors how--- 'Okf.Bundle' uses a @type_@ field to avoid clashing with the @type@ keyword.)-instance FromDhall TypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | Encode a profile so tooling can consume the same descriptor okf reads.--- Written by hand rather than derived so the key order is stable and, more--- importantly, so 'TypeRule' emits @type@ rather than the Haskell field name--- @type_@ — matching the Dhall field and how 'Okf.Graph.Node' already encodes.-instance ToJSON ProfileSpec where- toJSON ProfileSpec {name, description, guidance, okfVersion, frontmatter, allowUnknownTypes, allowUnknownFields, idField, requireBundleVersion, types = typeRules} =- object- [ "name" .= name,- "description" .= description,- "guidance" .= guidance,- "okfVersion" .= okfVersion,- "requireBundleVersion" .= requireBundleVersion,- "allowUnknownTypes" .= allowUnknownTypes,- "allowUnknownFields" .= allowUnknownFields,- "idField" .= idField,- "frontmatter" .= frontmatter,- "types" .= typeRules- ]--instance ToJSON FrontmatterRules where- toJSON FrontmatterRules {required, recommended, optional} =- object- [ "required" .= required,- "recommended" .= recommended,- "optional" .= optional- ]--instance ToJSON FieldCondition where- toJSON FieldCondition {field = fieldName, hasValue} =- object- [ "field" .= fieldName,- "hasValue" .= hasValue- ]--instance ToJSON HandleReferenceRule where- toJSON HandleReferenceRule {localPrefix, externalUriSchemes, allowSelf, allowLocal, externalUriPattern} =- object- [ "localPrefix" .= localPrefix,- "externalUriSchemes" .= externalUriSchemes,- "allowSelf" .= allowSelf,- "allowLocal" .= allowLocal,- "externalUriPattern" .= externalUriPattern- ]--instance ToJSON PathReferenceRule where- toJSON PathReferenceRule {externalUriSchemes, allowSelf} =- object- [ "externalUriSchemes" .= externalUriSchemes,- "allowSelf" .= allowSelf- ]--instance ToJSON FieldRule where- toJSON FieldRule {field = fieldName, description, allowedValues, cardinality, format, elementFields, objectFields, reference, path = pathRule, when = condition, uniqueBy} =- object- [ "field" .= fieldName,- "description" .= description,- "allowedValues" .= allowedValues,- "cardinality" .= cardinality,- "format" .= format,- "elementFields" .= elementFields,- "reference" .= reference,- "when" .= condition,- -- Appended rather than placed beside @elementFields@, so that the list- -- reads as "the keys this instance has always emitted, then the ones- -- added since". A consumer keys on names, not position.- "objectFields" .= objectFields,- "path" .= pathRule,- "uniqueBy" .= uniqueBy- ]--instance ToJSON NestedRules where- toJSON NestedRules {required, recommended, optional} =- object- [ "required" .= required,- "recommended" .= recommended,- "optional" .= optional- ]--instance ToJSON NestedFieldRule where- toJSON NestedFieldRule {field = fieldName, description, allowedValues, cardinality, format, path = pathRule, when = condition, reference} =- object- [ "field" .= fieldName,- "description" .= description,- "allowedValues" .= allowedValues,- "cardinality" .= cardinality,- "format" .= format,- "when" .= condition,- -- Appended for the same reason 'FieldRule' appends @objectFields@.- "path" .= pathRule,- "reference" .= reference- ]--instance ToJSON Cardinality where- toJSON = String . cardinalityName--cardinalityName :: Cardinality -> Text-cardinalityName = \case- Any -> "any"- Scalar -> "scalar"- List -> "list"- Object -> "object"--instance ToJSON FieldFormat where- toJSON = \case- Rfc3339Utc -> String "rfc3339-utc"- Date -> String "date"- Uri -> String "uri"- UriWithScheme scheme -> object ["uriWithScheme" .= scheme]- DocumentHandle prefix -> object ["documentHandle" .= prefix]- Actor -> String "actor"- HumanActor -> String "human-actor"- Integer -> String "integer"- NonNegativeInteger -> String "non-negative-integer"- Boolean -> String "boolean"--instance ToJSON TypeRule where- toJSON- TypeRule- { type_ = ruleType,- description,- guidance,- frontmatter,- pathPattern,- resourceScheme,- requireSchemaSection,- schemaColumns,- idPrefix- } =- object- [ "type" .= ruleType,- "description" .= description,- "guidance" .= guidance,- "frontmatter" .= frontmatter,- "pathPattern" .= pathPattern,- "resourceScheme" .= resourceScheme,- "requireSchemaSection" .= requireSchemaSection,- "schemaColumns" .= schemaColumns,- "idPrefix" .= idPrefix- ]---- | The complete descriptor generation published by okf-core 0.8.0.0, frozen--- immediately before profile and type guidance were added. Every reachable--- record and union is copied here so future schema additions cannot change the--- Dhall type this decoder accepts. Exercised by--- @okf-core/test/fixtures/profiles/pre-guidance-0.8.0.0.dhall@.-data PreGuidanceCardinality- = PreGuidanceAny- | PreGuidanceScalar- | PreGuidanceList- deriving stock (Generic, Eq, Ord, Show)--instance FromDhall PreGuidanceCardinality where- autoWith _normalizer =- Dhall.union- ( (PreGuidanceAny <$ Dhall.constructor "Any" Dhall.unit)- <> (PreGuidanceScalar <$ Dhall.constructor "Scalar" Dhall.unit)- <> (PreGuidanceList <$ Dhall.constructor "List" Dhall.unit)- )--data PreGuidanceFieldFormat- = PreGuidanceRfc3339Utc- | PreGuidanceDate- | PreGuidanceUri- | PreGuidanceUriWithScheme Text- | PreGuidanceDocumentHandle Text- | PreGuidanceActor- | PreGuidanceHumanActor- | PreGuidanceInteger- | PreGuidanceNonNegativeInteger- | PreGuidanceBoolean- deriving stock (Generic, Eq, Ord, Show)--instance FromDhall PreGuidanceFieldFormat where- autoWith _normalizer =- Dhall.union- ( (PreGuidanceRfc3339Utc <$ Dhall.constructor "Rfc3339Utc" Dhall.unit)- <> (PreGuidanceDate <$ Dhall.constructor "Date" Dhall.unit)- <> (PreGuidanceUri <$ Dhall.constructor "Uri" Dhall.unit)- <> (PreGuidanceUriWithScheme <$> Dhall.constructor "UriWithScheme" Dhall.auto)- <> (PreGuidanceDocumentHandle <$> Dhall.constructor "DocumentHandle" Dhall.auto)- <> (PreGuidanceActor <$ Dhall.constructor "Actor" Dhall.unit)- <> (PreGuidanceHumanActor <$ Dhall.constructor "HumanActor" Dhall.unit)- <> (PreGuidanceInteger <$ Dhall.constructor "Integer" Dhall.unit)- <> (PreGuidanceNonNegativeInteger <$ Dhall.constructor "NonNegativeInteger" Dhall.unit)- <> (PreGuidanceBoolean <$ Dhall.constructor "Boolean" Dhall.unit)- )--data PreGuidanceFieldCondition = PreGuidanceFieldCondition- { field :: !Text,- hasValue :: ![Text]- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)--data PreGuidanceHandleReferenceRule = PreGuidanceHandleReferenceRule- { localPrefix :: !Text,- externalUriSchemes :: ![Text],- allowSelf :: !Bool,- allowLocal :: !Bool,- externalUriPattern :: !(Maybe Text)- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)--data PreGuidancePathReferenceRule = PreGuidancePathReferenceRule- { externalUriSchemes :: ![Text],- allowSelf :: !Bool- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)--data PreGuidanceFieldRule = PreGuidanceFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !PreGuidanceCardinality,- format :: !(Maybe PreGuidanceFieldFormat),- elementFields :: !(Maybe PreGuidanceNestedRules),- objectFields :: !(Maybe PreGuidanceNestedRules),- reference :: !(Maybe PreGuidanceHandleReferenceRule),- path :: !(Maybe PreGuidancePathReferenceRule),- when :: !(Maybe PreGuidanceFieldCondition),- uniqueBy :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreGuidanceNestedRules = PreGuidanceNestedRules- { required :: ![PreGuidanceNestedFieldRule],- recommended :: ![PreGuidanceNestedFieldRule],- optional :: ![PreGuidanceNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreGuidanceNestedFieldRule = PreGuidanceNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !PreGuidanceCardinality,- format :: !(Maybe PreGuidanceFieldFormat),- path :: !(Maybe PreGuidancePathReferenceRule),- when :: !(Maybe PreGuidanceFieldCondition),- reference :: !(Maybe PreGuidanceHandleReferenceRule)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreGuidanceFrontmatterRules = PreGuidanceFrontmatterRules- { required :: ![PreGuidanceFieldRule],- recommended :: ![PreGuidanceFieldRule],- optional :: ![PreGuidanceFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreGuidanceTypeRule = PreGuidanceTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PreGuidanceFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall PreGuidanceTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)--data PreGuidanceProfileSpec = PreGuidanceProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreGuidanceFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- requireBundleVersion :: !(Maybe Text),- types :: ![PreGuidanceTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The okf 0.2.x profile record: frontmatter keys were bare strings and--- nothing carried a description. Decoded only as a fallback, so descriptors--- written before descriptions existed keep loading unchanged. Deliberately--- private and deliberately frozen — it is a record of a retired shape, not a--- second profile model. Exercised by--- @okf-core\/test\/fixtures\/profiles\/legacy-0.2.dhall@.-data LegacyProfileSpec = LegacyProfileSpec- { name :: !Text,- okfVersion :: !Text,- frontmatter :: !LegacyFrontmatterRules,- allowUnknownTypes :: !Bool,- idField :: !(Maybe Text),- types :: ![LegacyTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The okf 0.2.x frontmatter record: two lists of bare key names.-data LegacyFrontmatterRules = LegacyFrontmatterRules- { required :: ![Text],- recommended :: ![Text]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The okf 0.2.x per-@type@ rule: today's 'TypeRule' without descriptions or--- type-specific frontmatter.-data LegacyTypeRule = LegacyTypeRule- { type_ :: !Text,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall LegacyTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The five-alternative published format union, frozen before the OKF v0.2--- value formats were added.------ Unlike every other frozen shape below this is a /union/ rather than a record.--- A Dhall union value carries its full alternative set in its type, so a--- descriptor pinned to the five-alternative--- @okf-core\/dhall\/FieldFormat.dhall@ does not typecheck against the current--- decoder, and no record-level fallback can repair that: every frozen generation--- would still refer to the widened 'FieldFormat'. Every frozen generation below--- therefore refers to this type instead.------ The instance is hand-written rather than derived so that the Dhall alternative--- names stay @Rfc3339Utc@ and friends while the Haskell constructors stay--- distinct from the current type's.-data PreV02FieldFormat- = LegacyRfc3339Utc- | LegacyDate- | LegacyUri- | LegacyUriWithScheme Text- | LegacyDocumentHandle Text- deriving stock (Generic, Eq, Ord, Show)--instance FromDhall PreV02FieldFormat where- autoWith _normalizer =- Dhall.union- ( (LegacyRfc3339Utc <$ Dhall.constructor "Rfc3339Utc" Dhall.unit)- <> (LegacyDate <$ Dhall.constructor "Date" Dhall.unit)- <> (LegacyUri <$ Dhall.constructor "Uri" Dhall.unit)- <> (LegacyUriWithScheme <$> Dhall.constructor "UriWithScheme" Dhall.auto)- <> (LegacyDocumentHandle <$> Dhall.constructor "DocumentHandle" Dhall.auto)- )--upgradePreV02FieldFormat :: PreV02FieldFormat -> FieldFormat-upgradePreV02FieldFormat = \case- LegacyRfc3339Utc -> Rfc3339Utc- LegacyDate -> Date- LegacyUri -> Uri- LegacyUriWithScheme scheme -> UriWithScheme scheme- LegacyDocumentHandle prefix -> DocumentHandle prefix---- | The complete 0.7.0.0 descriptor generation, frozen before nested--- document-reference policies and record-list uniqueness were added. Every--- record that directly or transitively contains one of the grown records is--- copied so a descriptor pinned to 0.7.0.0 remains decodable as one closed--- Dhall type.-data PreNestedReferenceHandleReferenceRule = PreNestedReferenceHandleReferenceRule- { localPrefix :: !Text,- externalUriSchemes :: ![Text],- allowSelf :: !Bool- }- deriving stock (Generic, Eq, Ord, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceFieldRule = PreNestedReferenceFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- elementFields :: !(Maybe PreNestedReferenceNestedRules),- objectFields :: !(Maybe PreNestedReferenceNestedRules),- reference :: !(Maybe PreNestedReferenceHandleReferenceRule),- path :: !(Maybe PathReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceNestedRules = PreNestedReferenceNestedRules- { required :: ![PreNestedReferenceNestedFieldRule],- recommended :: ![PreNestedReferenceNestedFieldRule],- optional :: ![PreNestedReferenceNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceNestedFieldRule = PreNestedReferenceNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- path :: !(Maybe PathReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceFrontmatterRules = PreNestedReferenceFrontmatterRules- { required :: ![PreNestedReferenceFieldRule],- recommended :: ![PreNestedReferenceFieldRule],- optional :: ![PreNestedReferenceFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceProfileSpec = PreNestedReferenceProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreNestedReferenceFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- requireBundleVersion :: !(Maybe Text),- types :: ![PreNestedReferenceTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreNestedReferenceTypeRule = PreNestedReferenceTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PreNestedReferenceFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall PreNestedReferenceTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete descriptor generation frozen before a profile could require its--- bundle to declare an OKF version. This is the immediately preceding public--- descriptor generation: it is today's shape minus the @requireBundleVersion@--- member on the top-level record. That member is the only difference, so every--- rule record, every type rule, and every union is unchanged and is shared--- rather than copied — the freezing rule of--- @docs\/adr\/11-growing-the-profile-descriptor-language.md@ is about the--- descriptor as a whole, and a generation that changes one record copies only--- what changed. Exercised by--- @okf-core\/test\/fixtures\/profiles\/pre-bundle-version.dhall@.-data PreBundleVersionProfileSpec = PreBundleVersionProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreNestedReferenceFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![PreNestedReferenceTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The complete descriptor generation frozen before path-valued reference--- rules were added: today's shape minus the @path@ member on 'FieldRule' and on--- 'NestedFieldRule'. Because that is a record addition rather than a union--- widening, 'Cardinality', 'FieldFormat', 'FieldCondition', and--- 'HandleReferenceRule' are unchanged by it and so are shared rather than--- copied; the two rule records and everything that contains them are copied.--- Exercised by @okf-core\/test\/fixtures\/profiles\/path-references-mp8-ep3.dhall@.-data PrePathProfileFieldRule = PrePathProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- elementFields :: !(Maybe PrePathProfileNestedRules),- objectFields :: !(Maybe PrePathProfileNestedRules),- reference :: !(Maybe PreNestedReferenceHandleReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PrePathProfileNestedRules = PrePathProfileNestedRules- { required :: ![PrePathProfileNestedFieldRule],- recommended :: ![PrePathProfileNestedFieldRule],- optional :: ![PrePathProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PrePathProfileNestedFieldRule = PrePathProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PrePathProfileFrontmatterRules = PrePathProfileFrontmatterRules- { required :: ![PrePathProfileFieldRule],- recommended :: ![PrePathProfileFieldRule],- optional :: ![PrePathProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PrePathProfileSpec = PrePathProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PrePathProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![PrePathProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PrePathProfileTypeRule = PrePathProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PrePathProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall PrePathProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete object-rule descriptor generation, frozen before the OKF v0.2--- value formats were added to 'FieldFormat'. Its records match the shape--- 'PrePathProfileFieldRule' froze — no @path@ member — and the further--- difference is that every @format@ member refers to the frozen--- five-alternative 'PreV02FieldFormat'. Exercised by--- @okf-core\/test\/fixtures\/profiles\/formats-mp8-ep2.dhall@.-data PreActorProfileFieldRule = PreActorProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- elementFields :: !(Maybe PreActorProfileNestedRules),- objectFields :: !(Maybe PreActorProfileNestedRules),- reference :: !(Maybe PreNestedReferenceHandleReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreActorProfileNestedRules = PreActorProfileNestedRules- { required :: ![PreActorProfileNestedFieldRule],- recommended :: ![PreActorProfileNestedFieldRule],- optional :: ![PreActorProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreActorProfileNestedFieldRule = PreActorProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreActorProfileFrontmatterRules = PreActorProfileFrontmatterRules- { required :: ![PreActorProfileFieldRule],- recommended :: ![PreActorProfileFieldRule],- optional :: ![PreActorProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreActorProfileSpec = PreActorProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreActorProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![PreActorProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreActorProfileTypeRule = PreActorProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PreActorProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall PreActorProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete optional-presence descriptor generation, frozen before object--- rules were added: it matches the shape before @objectFields@ was added to--- 'FieldRule'. 'Cardinality', 'HandleReferenceRule', and 'FieldCondition' are--- unchanged by every addition since and so are shared rather than copied; the--- nested rule types are copied because their @format@ member now refers to the--- frozen 'PreV02FieldFormat'. Exercised by--- @okf-core\/test\/fixtures\/profiles\/object-fields-mp8-ep1.dhall@.-data PreObjectProfileFieldRule = PreObjectProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- elementFields :: !(Maybe PreObjectProfileNestedRules),- reference :: !(Maybe PreNestedReferenceHandleReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreObjectProfileNestedRules = PreObjectProfileNestedRules- { required :: ![PreObjectProfileNestedFieldRule],- recommended :: ![PreObjectProfileNestedFieldRule],- optional :: ![PreObjectProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreObjectProfileNestedFieldRule = PreObjectProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreObjectProfileFrontmatterRules = PreObjectProfileFrontmatterRules- { required :: ![PreObjectProfileFieldRule],- recommended :: ![PreObjectProfileFieldRule],- optional :: ![PreObjectProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreObjectProfileSpec = PreObjectProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreObjectProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![PreObjectProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreObjectProfileTypeRule = PreObjectProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PreObjectProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall PreObjectProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete reference-aware descriptor generation, frozen before the--- @optional@ presence list was added to 'FrontmatterRules' and 'NestedRules'.--- This is the immediately preceding public descriptor generation: it matches--- today's shape exactly apart from that third list, so every descriptor written--- as a record literal against the published schema keeps loading. Exercised by--- @okf-core\/test\/fixtures\/profiles\/document-references-ep3.dhall@.-data ReferenceProfileFieldRule = ReferenceProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- elementFields :: !(Maybe ReferenceProfileNestedRules),- reference :: !(Maybe PreNestedReferenceHandleReferenceRule),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ReferenceProfileNestedRules = ReferenceProfileNestedRules- { required :: ![ReferenceProfileNestedFieldRule],- recommended :: ![ReferenceProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ReferenceProfileNestedFieldRule = ReferenceProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ReferenceProfileFrontmatterRules = ReferenceProfileFrontmatterRules- { required :: ![ReferenceProfileFieldRule],- recommended :: ![ReferenceProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ReferenceProfileSpec = ReferenceProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !ReferenceProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![ReferenceProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ReferenceProfileTypeRule = ReferenceProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !ReferenceProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall ReferenceProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete condition-aware descriptor generation, frozen before--- top-level document-reference policies were added.-data ConditionalProfileFieldRule = ConditionalProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- elementFields :: !(Maybe ConditionalProfileNestedRules),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ConditionalProfileNestedRules = ConditionalProfileNestedRules- { required :: ![ConditionalProfileNestedFieldRule],- recommended :: ![ConditionalProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ConditionalProfileNestedFieldRule = ConditionalProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- when :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ConditionalProfileFrontmatterRules = ConditionalProfileFrontmatterRules- { required :: ![ConditionalProfileFieldRule],- recommended :: ![ConditionalProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ConditionalProfileSpec = ConditionalProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !ConditionalProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![ConditionalProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data ConditionalProfileTypeRule = ConditionalProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !ConditionalProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall ConditionalProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete bounded-nested descriptor generation, frozen before--- same-scope field conditions were added. This is the immediately preceding--- public descriptor generation.-data NestedProfileFieldRule = NestedProfileFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat),- elementFields :: !(Maybe NestedProfileRules)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data NestedProfileRules = NestedProfileRules- { required :: ![NestedProfileNestedFieldRule],- recommended :: ![NestedProfileNestedFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data NestedProfileNestedFieldRule = NestedProfileNestedFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data NestedProfileFrontmatterRules = NestedProfileFrontmatterRules- { required :: ![NestedProfileFieldRule],- recommended :: ![NestedProfileFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data NestedProfileSpec = NestedProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !NestedProfileFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![NestedProfileTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data NestedProfileTypeRule = NestedProfileTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !NestedProfileFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall NestedProfileTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The complete EP-4 field rule, frozen before one-level nested records were--- added. This is the immediately preceding public descriptor generation.-data FormatFieldRule = FormatFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe PreV02FieldFormat)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data FormatFrontmatterRules = FormatFrontmatterRules- { required :: ![FormatFieldRule],- recommended :: ![FormatFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data FormatProfileSpec = FormatProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !FormatFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![FormatTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data FormatTypeRule = FormatTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !FormatFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall FormatTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The EP-3 field rule, frozen before named formats were added.-data CardinalityFieldRule = CardinalityFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data CardinalityFrontmatterRules = CardinalityFrontmatterRules- { required :: ![CardinalityFieldRule],- recommended :: ![CardinalityFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The complete EP-3 profile shape, frozen before named formats were added.-data CardinalityProfileSpec = CardinalityProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !CardinalityFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![CardinalityTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data CardinalityTypeRule = CardinalityTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !CardinalityFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall CardinalityTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The EP-2 field rule, frozen before cardinality was added.-data VocabularyFieldRule = VocabularyFieldRule- { field :: !Text,- description :: !(Maybe Text),- allowedValues :: ![Text]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data VocabularyFrontmatterRules = VocabularyFrontmatterRules- { required :: ![VocabularyFieldRule],- recommended :: ![VocabularyFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The EP-2 profile shape, frozen before cardinality was added.-data VocabularyProfileSpec = VocabularyProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !VocabularyFrontmatterRules,- allowUnknownTypes :: !Bool,- allowUnknownFields :: !Bool,- idField :: !(Maybe Text),- types :: ![VocabularyTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data VocabularyTypeRule = VocabularyTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !VocabularyFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall VocabularyTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | A field rule from either self-documenting schema generation, before value--- vocabularies were added.-data PreviousFieldRule = PreviousFieldRule- { field :: !Text,- description :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data PreviousFrontmatterRules = PreviousFrontmatterRules- { required :: ![PreviousFieldRule],- recommended :: ![PreviousFieldRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)---- | The type-aware shape from EP-1, frozen before vocabularies and field-name--- closure were added.-data TypeAwareProfileSpec = TypeAwareProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreviousFrontmatterRules,- allowUnknownTypes :: !Bool,- idField :: !(Maybe Text),- types :: ![TypeAwareTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data TypeAwareTypeRule = TypeAwareTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- frontmatter :: !PreviousFrontmatterRules,- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall TypeAwareTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)---- | The self-documenting profile shape published immediately before--- type-specific frontmatter rules. It is frozen as a compatibility decoder in--- exactly the same way as the older 0.2.x shape below.-data DescribedProfileSpec = DescribedProfileSpec- { name :: !Text,- description :: !(Maybe Text),- okfVersion :: !Text,- frontmatter :: !PreviousFrontmatterRules,- allowUnknownTypes :: !Bool,- idField :: !(Maybe Text),- types :: ![DescribedTypeRule]- }- deriving stock (Generic, Eq, Show)- deriving anyclass (FromDhall)--data DescribedTypeRule = DescribedTypeRule- { type_ :: !Text,- description :: !(Maybe Text),- pathPattern :: !(Maybe Text),- resourceScheme :: !(Maybe Text),- requireSchemaSection :: !Bool,- schemaColumns :: ![Text],- idPrefix :: !(Maybe Text)- }- deriving stock (Generic, Eq, Show)--instance FromDhall DescribedTypeRule where- autoWith _normalizer =- genericAutoWith- (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})- where- stripTrailingUnderscore fieldName =- fromMaybe fieldName (Text.stripSuffix "_" fieldName)--emptyFrontmatterRules :: FrontmatterRules-emptyFrontmatterRules = FrontmatterRules {required = [], recommended = [], optional = []}--upgradePreGuidanceCardinality :: PreGuidanceCardinality -> Cardinality-upgradePreGuidanceCardinality = \case- PreGuidanceAny -> Any- PreGuidanceScalar -> Scalar- PreGuidanceList -> List--upgradePreGuidanceFieldFormat :: PreGuidanceFieldFormat -> FieldFormat-upgradePreGuidanceFieldFormat = \case- PreGuidanceRfc3339Utc -> Rfc3339Utc- PreGuidanceDate -> Date- PreGuidanceUri -> Uri- PreGuidanceUriWithScheme scheme -> UriWithScheme scheme- PreGuidanceDocumentHandle prefix -> DocumentHandle prefix- PreGuidanceActor -> Actor- PreGuidanceHumanActor -> HumanActor- PreGuidanceInteger -> Integer- PreGuidanceNonNegativeInteger -> NonNegativeInteger- PreGuidanceBoolean -> Boolean--upgradePreGuidanceCondition :: PreGuidanceFieldCondition -> FieldCondition-upgradePreGuidanceCondition previous =- FieldCondition- { field = previous ^. #field,- hasValue = previous ^. #hasValue- }--upgradePreGuidanceHandleRule :: PreGuidanceHandleReferenceRule -> HandleReferenceRule-upgradePreGuidanceHandleRule previous =- HandleReferenceRule- { localPrefix = previous ^. #localPrefix,- externalUriSchemes = previous ^. #externalUriSchemes,- allowSelf = previous ^. #allowSelf,- allowLocal = previous ^. #allowLocal,- externalUriPattern = previous ^. #externalUriPattern- }--upgradePreGuidancePathRule :: PreGuidancePathReferenceRule -> PathReferenceRule-upgradePreGuidancePathRule previous =- PathReferenceRule- { externalUriSchemes = previous ^. #externalUriSchemes,- allowSelf = previous ^. #allowSelf- }--upgradePreGuidanceFrontmatter :: PreGuidanceFrontmatterRules -> FrontmatterRules-upgradePreGuidanceFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = map upgradeField (previous ^. #optional)- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),- format = upgradePreGuidanceFieldFormat <$> rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = upgradeNestedRules <$> rule ^. #objectFields,- reference = upgradePreGuidanceHandleRule <$> rule ^. #reference,- path = upgradePreGuidancePathRule <$> rule ^. #path,- when = upgradePreGuidanceCondition <$> rule ^. #when,- uniqueBy = rule ^. #uniqueBy- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = map upgradeNestedField (rules ^. #optional)- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),- format = upgradePreGuidanceFieldFormat <$> rule ^. #format,- path = upgradePreGuidancePathRule <$> rule ^. #path,- when = upgradePreGuidanceCondition <$> rule ^. #when,- reference = upgradePreGuidanceHandleRule <$> rule ^. #reference- }--upgradePreGuidanceTypeRule :: PreGuidanceTypeRule -> TypeRule-upgradePreGuidanceTypeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePreGuidanceFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradePreGuidanceProfile :: PreGuidanceProfileSpec -> ProfileSpec-upgradePreGuidanceProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreGuidanceFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = previous ^. #requireBundleVersion,- types = map upgradePreGuidanceTypeRule (previous ^. #types)- }--upgradePreNestedReferenceHandleRule :: PreNestedReferenceHandleReferenceRule -> HandleReferenceRule-upgradePreNestedReferenceHandleRule previous =- HandleReferenceRule- { localPrefix = previous ^. #localPrefix,- externalUriSchemes = previous ^. #externalUriSchemes,- allowSelf = previous ^. #allowSelf,- allowLocal = True,- externalUriPattern = Nothing- }--upgradePreNestedReferenceFrontmatter :: PreNestedReferenceFrontmatterRules -> FrontmatterRules-upgradePreNestedReferenceFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = map upgradeField (previous ^. #optional)- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = upgradeNestedRules <$> rule ^. #objectFields,- reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,- path = rule ^. #path,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = map upgradeNestedField (rules ^. #optional)- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = rule ^. #format,- path = rule ^. #path,- when = rule ^. #when,- reference = Nothing- }--upgradePreNestedReferenceTypeRule :: PreNestedReferenceTypeRule -> TypeRule-upgradePreNestedReferenceTypeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePreNestedReferenceFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradePreNestedReferenceProfile :: PreNestedReferenceProfileSpec -> ProfileSpec-upgradePreNestedReferenceProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreNestedReferenceFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = previous ^. #requireBundleVersion,- types = map upgradePreNestedReferenceTypeRule (previous ^. #types)- }--upgradePrePathProfileFrontmatter :: PrePathProfileFrontmatterRules -> FrontmatterRules-upgradePrePathProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = map upgradeField (previous ^. #optional)- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = upgradeNestedRules <$> rule ^. #objectFields,- reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,- path = Nothing,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = map upgradeNestedField (rules ^. #optional)- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = rule ^. #format,- path = Nothing,- when = rule ^. #when,- reference = Nothing- }--upgradePreActorProfileFrontmatter :: PreActorProfileFrontmatterRules -> FrontmatterRules-upgradePreActorProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = map upgradeField (previous ^. #optional)- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = upgradeNestedRules <$> rule ^. #objectFields,- reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,- path = Nothing,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = map upgradeNestedField (rules ^. #optional)- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- path = Nothing,- when = rule ^. #when,- reference = Nothing- }--upgradePreObjectProfileFrontmatter :: PreObjectProfileFrontmatterRules -> FrontmatterRules-upgradePreObjectProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = map upgradeField (previous ^. #optional)- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = Nothing,- reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,- path = Nothing,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = map upgradeNestedField (rules ^. #optional)- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- path = Nothing,- when = rule ^. #when,- reference = Nothing- }--upgradeReferenceProfileFrontmatter :: ReferenceProfileFrontmatterRules -> FrontmatterRules-upgradeReferenceProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = Nothing,- reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,- path = Nothing,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = []- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- path = Nothing,- when = rule ^. #when,- reference = Nothing- }--upgradePreviousFrontmatter :: PreviousFrontmatterRules -> FrontmatterRules-upgradePreviousFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = [],- cardinality = Any,- format = Nothing,- elementFields = Nothing,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = Nothing,- uniqueBy = Nothing- }--upgradeConditionalProfileFrontmatter :: ConditionalProfileFrontmatterRules -> FrontmatterRules-upgradeConditionalProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = upgradeNestedRules <$> rule ^. #elementFields,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = rule ^. #when,- uniqueBy = Nothing- }- upgradeNestedRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = []- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- path = Nothing,- when = rule ^. #when,- reference = Nothing- }--upgradeNestedProfileFrontmatter :: NestedProfileFrontmatterRules -> FrontmatterRules-upgradeNestedProfileFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = upgradeNestedProfileRules <$> rule ^. #elementFields,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = Nothing,- uniqueBy = Nothing- }- upgradeNestedProfileRules rules =- NestedRules- { required = map upgradeNestedField (rules ^. #required),- recommended = map upgradeNestedField (rules ^. #recommended),- optional = []- }- upgradeNestedField rule =- NestedFieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- path = Nothing,- when = Nothing,- reference = Nothing- }--upgradeFormatFrontmatter :: FormatFrontmatterRules -> FrontmatterRules-upgradeFormatFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = upgradePreV02FieldFormat <$> rule ^. #format,- elementFields = Nothing,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = Nothing,- uniqueBy = Nothing- }--upgradeCardinalityFrontmatter :: CardinalityFrontmatterRules -> FrontmatterRules-upgradeCardinalityFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = rule ^. #cardinality,- format = Nothing,- elementFields = Nothing,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = Nothing,- uniqueBy = Nothing- }--upgradeVocabularyFrontmatter :: VocabularyFrontmatterRules -> FrontmatterRules-upgradeVocabularyFrontmatter previous =- FrontmatterRules- { required = map upgradeField (previous ^. #required),- recommended = map upgradeField (previous ^. #recommended),- optional = []- }- where- upgradeField rule =- FieldRule- { field = rule ^. #field,- description = rule ^. #description,- allowedValues = rule ^. #allowedValues,- cardinality = Any,- format = Nothing,- elementFields = Nothing,- objectFields = Nothing,- reference = Nothing,- path = Nothing,- when = Nothing,- uniqueBy = Nothing- }---- | Lift the generation frozen before @requireBundleVersion@ forward. Its--- contained rule records use the shared 0.7.0.0 frozen types because those--- records later grew too.-upgradePreBundleVersionProfile :: PreBundleVersionProfileSpec -> ProfileSpec-upgradePreBundleVersionProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreNestedReferenceFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradePreNestedReferenceTypeRule (previous ^. #types)- }--upgradePrePathProfile :: PrePathProfileSpec -> ProfileSpec-upgradePrePathProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePrePathProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePrePathProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradePreActorProfile :: PreActorProfileSpec -> ProfileSpec-upgradePreActorProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreActorProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePreActorProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradePreObjectProfile :: PreObjectProfileSpec -> ProfileSpec-upgradePreObjectProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreObjectProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePreObjectProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeReferenceProfile :: ReferenceProfileSpec -> ProfileSpec-upgradeReferenceProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeReferenceProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeReferenceProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeConditionalProfile :: ConditionalProfileSpec -> ProfileSpec-upgradeConditionalProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeConditionalProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeConditionalProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeNestedProfile :: NestedProfileSpec -> ProfileSpec-upgradeNestedProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeNestedProfileFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeNestedProfileFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeFormatProfile :: FormatProfileSpec -> ProfileSpec-upgradeFormatProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeFormatFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeFormatFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeCardinalityProfile :: CardinalityProfileSpec -> ProfileSpec-upgradeCardinalityProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeCardinalityFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeCardinalityFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeVocabularyProfile :: VocabularyProfileSpec -> ProfileSpec-upgradeVocabularyProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradeVocabularyFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = previous ^. #allowUnknownFields,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradeVocabularyFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeTypeAwareProfile :: TypeAwareProfileSpec -> ProfileSpec-upgradeTypeAwareProfile previous =- ProfileSpec- { name = previous ^. #name,- description = previous ^. #description,- guidance = Nothing,- okfVersion = previous ^. #okfVersion,- frontmatter = upgradePreviousFrontmatter (previous ^. #frontmatter),- allowUnknownTypes = previous ^. #allowUnknownTypes,- allowUnknownFields = True,- idField = previous ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (previous ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = upgradePreviousFrontmatter (rule ^. #frontmatter),- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }--upgradeDescribedProfile :: DescribedProfileSpec -> ProfileSpec-upgradeDescribedProfile described =- ProfileSpec- { name = described ^. #name,- description = described ^. #description,- guidance = Nothing,- okfVersion = described ^. #okfVersion,- frontmatter = upgradePreviousFrontmatter (described ^. #frontmatter),- allowUnknownTypes = described ^. #allowUnknownTypes,- allowUnknownFields = True,- idField = described ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (described ^. #types)- }- where- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = rule ^. #description,- guidance = Nothing,- frontmatter = emptyFrontmatterRules,- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }---- | Lift a 0.2.x profile into the current shape by attaching neither--- descriptions nor guidance, and by supplying empty type-specific frontmatter.-upgradeLegacyProfile :: LegacyProfileSpec -> ProfileSpec-upgradeLegacyProfile legacy =- ProfileSpec- { name = legacy ^. #name,- description = Nothing,- guidance = Nothing,- okfVersion = legacy ^. #okfVersion,- frontmatter =- FrontmatterRules- { required = map undocumented (legacy ^. #frontmatter . #required),- recommended = map undocumented (legacy ^. #frontmatter . #recommended),- optional = []- },- allowUnknownTypes = legacy ^. #allowUnknownTypes,- allowUnknownFields = True,- idField = legacy ^. #idField,- requireBundleVersion = Nothing,- types = map upgradeRule (legacy ^. #types)- }- where- undocumented key = FieldRule {field = key, description = Nothing, allowedValues = [], cardinality = Any, format = Nothing, elementFields = Nothing, objectFields = Nothing, reference = Nothing, path = Nothing, when = Nothing, uniqueBy = Nothing}- upgradeRule rule =- TypeRule- { type_ = rule ^. #type_,- description = Nothing,- guidance = Nothing,- frontmatter = emptyFrontmatterRules,- pathPattern = rule ^. #pathPattern,- resourceScheme = rule ^. #resourceScheme,- requireSchemaSection = rule ^. #requireSchemaSection,- schemaColumns = rule ^. #schemaColumns,- idPrefix = rule ^. #idPrefix- }---- | Load and decode a Dhall profile descriptor from a file path. Any evaluation--- or decoding failure is captured as a human-readable 'Left'.------ The pre-guidance shape, pre-nested-reference shape, pre-bundle-version shape,--- pre-path shape, pre-actor shape, pre-object--- shape, reference-aware shape,--- condition-aware shape, bounded-nested shape, EP-4--- format shape, EP-3 cardinality shape, EP-2 vocabulary shape, type-aware EP-1--- shape, self-documenting shape, and okf 0.2.x shape are accepted by frozen--- fallback decoders and upgraded--- with their no-op defaults. When every decoder fails, the /current/ decoder's--- error is reported.-loadProfileFile :: FilePath -> IO (Either Text ProfileSpec)-loadProfileFile path = do- current <- tryDecode (Dhall.inputFile auto path)- case current of- Right spec -> pure (Right spec)- -- Only the current decoder's error is kept. An author wants to know how- -- their descriptor differs from today's schema, not from a retired one.- Left currentError -> maybe (Left currentError) Right <$> firstFrozen frozenDecoders- where- -- Newest generation first. Each entry looks identical but is inferred at a- -- distinct result type, fixed by the upgrade function it names; @auto@ then- -- picks that generation's decoder. Adding a generation is one line here.- frozenDecoders :: [IO (Maybe ProfileSpec)]- frozenDecoders =- [ attempt upgradePreGuidanceProfile,- attempt upgradePreNestedReferenceProfile,- attempt upgradePreBundleVersionProfile,- attempt upgradePrePathProfile,- attempt upgradePreActorProfile,- attempt upgradePreObjectProfile,- attempt upgradeReferenceProfile,- attempt upgradeConditionalProfile,- attempt upgradeNestedProfile,- attempt upgradeFormatProfile,- attempt upgradeCardinalityProfile,- attempt upgradeVocabularyProfile,- attempt upgradeTypeAwareProfile,- attempt upgradeDescribedProfile,- attempt upgradeLegacyProfile- ]-- -- Short-circuiting, so a descriptor that decodes at the first frozen- -- generation costs one extra parse rather than thirteen.- firstFrozen :: [IO (Maybe ProfileSpec)] -> IO (Maybe ProfileSpec)- firstFrozen [] = pure Nothing- firstFrozen (decoder : remaining) =- decoder >>= \case- Just spec -> pure (Just spec)- Nothing -> firstFrozen remaining-- attempt :: (FromDhall a) => (a -> ProfileSpec) -> IO (Maybe ProfileSpec)- attempt upgrade = either (const Nothing) (Just . upgrade) <$> tryDecode (Dhall.inputFile auto path)-- tryDecode :: IO a -> IO (Either Text a)- tryDecode action =- (Right <$> action)- `catch` \(exception :: SomeException) -> pure (Left (Text.pack (show exception)))---- | Does an already-evaluated Dhall expression decode as a profile? Tries the--- current schema, then the pre-guidance, pre-nested-reference,--- pre-bundle-version, pre-path, pre-actor, pre-object,--- reference-aware,--- condition-aware, bounded-nested, EP-4, EP-3, EP-2, EP-1, self-documenting, and--- okf 0.2.x schemas, so the published @okf-profiles@ package still enumerates.--- Uses 'Dhall.rawInput', which normalizes and runs the decoder's extractor--- without throwing, which is what lets registry enumeration be pure.-decodeProfileExpr :: Expr Src Void -> Maybe ProfileSpec-decodeProfileExpr expression =- Dhall.rawInput Dhall.auto expression- <|> fmap upgradePreGuidanceProfile (Dhall.rawInput Dhall.auto expression)- <|> decodeProfilesBeforeGuidance expression---- | The generic Dhall record extractor deliberately ignores extra record--- members. Without this guard, a value from the 0.8.0.0 generation can fall--- through to an older decoder and silently lose nested-reference or uniqueness--- declarations. Every non-empty 0.8.0.0 field-rule value carries at least one--- of these post-0.7 record members, even when its value is @None@.-decodeProfilesBeforeGuidance :: Expr Src Void -> Maybe ProfileSpec-decodeProfilesBeforeGuidance expression- | containsPost07DescriptorField expression = Nothing- | otherwise =- fmap upgradePreNestedReferenceProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradePreBundleVersionProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradePrePathProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradePreActorProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradePreObjectProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeReferenceProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeConditionalProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeNestedProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeFormatProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeCardinalityProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeVocabularyProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeTypeAwareProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeDescribedProfile (Dhall.rawInput Dhall.auto expression)- <|> fmap upgradeLegacyProfile (Dhall.rawInput Dhall.auto expression)--containsPost07DescriptorField :: Expr Src Void -> Bool-containsPost07DescriptorField =- anyOf (cosmosOf Dhall.Core.subExpressions) $ \case- Dhall.Core.RecordLit fields ->- any (`Dhall.Map.member` fields) ["uniqueBy", "allowLocal", "externalUriPattern"]- _ -> False---- | The description a profile attaches to a frontmatter key, looking in--- @required@ first, then @recommended@, then @optional@. 'Nothing' when the key--- is undocumented or absent from the profile entirely.-profileFieldDescription :: ProfileSpec -> Text -> Maybe Text-profileFieldDescription spec key =- case [rule | rule <- rules, rule ^. #field == key] of- (rule : _) -> rule ^. #description- [] -> Nothing- where- rules =- spec ^. #frontmatter . #required- <> spec ^. #frontmatter . #recommended- <> spec ^. #frontmatter . #optional---- | A malformed profile definition. The optional type is absent for profile--- scope and present for a type-specific scope. The list name is @required@,--- @recommended@, or @optional@.-data ProfileDefinitionError- = DuplicateTypeRule Text- | DuplicateFieldRule (Maybe Text) Text Text- | ConflictingFieldRequirement (Maybe Text) Text- | UnsatisfiableVocabulary (Maybe Text) Text [Text] [Text]- | ConflictingCardinality (Maybe Text) Text Cardinality Cardinality- | ElementFieldsRequireList (Maybe Text) FieldPath Cardinality- | InvalidFormatParameter FieldPath FieldFormat Text- | ConflictingFieldFormat FieldPath FieldFormat FieldFormat- | EmptyConditionValues (Maybe Text) FieldPath FieldPath- | ConditionFieldNotDeclared (Maybe Text) FieldPath FieldPath- | ConditionFieldNotScalar (Maybe Text) FieldPath FieldPath Cardinality- | ConditionFieldOpenVocabulary (Maybe Text) FieldPath FieldPath- | ConditionFieldHasUnreachableValues (Maybe Text) FieldPath FieldPath [Text] [Text]- | SelfConditionalField (Maybe Text) FieldPath- | InvalidReferencePrefix (Maybe Text) FieldPath Text- | ReferencePrefixNotDeclared (Maybe Text) FieldPath Text- | ReferenceRequiresIdField (Maybe Text) FieldPath- | InvalidExternalReferenceScheme (Maybe Text) FieldPath Text- | ConflictingReferencePrefix Text FieldPath Text Text- | ReferenceWithFormat (Maybe Text) FieldPath FieldFormat- | -- | an @optional@ rule carries a @when@ condition, which gates only presence- -- and is therefore dead: an optional rule has no presence check at all- OptionalFieldWithCondition (Maybe Text) FieldPath- | -- | a rule declares @objectFields@ alongside an explicit scalar or list- -- cardinality; an object is neither, so the pairing cannot be satisfied- ObjectFieldsRequireObjectShape (Maybe Text) FieldPath Cardinality- | -- | one rule declares both a document-handle policy and a path policy;- -- a value cannot be resolved as both a handle and a path- PathReferenceWithHandleReference (Maybe Text) FieldPath- | InvalidExternalUriPattern (Maybe Text) FieldPath Text Text- | ConflictingExternalUriPatterns Text FieldPath Text Text- | UniqueByRequiresElementFields (Maybe Text) FieldPath Text- | UniqueByFieldNotDeclared (Maybe Text) FieldPath- | UniqueByFieldNotUnconditionallyRequired (Maybe Text) FieldPath- | UniqueByFieldNotScalar (Maybe Text) FieldPath Cardinality- | ConflictingUniqueBy Text FieldPath Text Text- | -- | @okfVersion@ is not @\<major\>.\<minor\>@- InvalidProfileOkfVersion Text- | -- | @okfVersion@ names a major version okf does not implement, so okf cannot- -- know which of its rules still hold- ProfileOkfVersionNotUnderstood Text- | -- | @requireBundleVersion@ is not @\<major\>.\<minor\>@, so no bundle- -- declaration could ever be compared against it- InvalidRequiredBundleVersion Text- | -- | a required or recommended rule names a key the declared version- -- supersedes (scope, path, declared version, version that superseded the key)- FieldSupersededInOkfVersion (Maybe Text) FieldPath Text Text- | -- | a rule names a value format introduced after the declared version- -- (scope, path, format, declared version, version that introduced the format)- FormatRequiresOkfVersion (Maybe Text) FieldPath FieldFormat Text Text- deriving stock (Generic, Eq, Ord, Show)---- | Whether a 'PresenceClause' demands a key or merely recommends it. There is--- deliberately no @OptionalField@ constructor: an optional key is encoded as an--- 'EffectiveFieldRule' with /no/ presence clauses at all.-data FieldRequirement = RecommendedField | RequiredField- deriving stock (Generic, Eq, Ord, Show)---- | One reason a key may have to be present, optionally gated by a condition on--- another same-scope key. Abstract: read it with 'presenceClauseRequirement' and--- 'presenceClauseCondition'.-data PresenceClause = PresenceClause- { requirement :: !FieldRequirement,- condition :: !(Maybe FieldCondition)- }- deriving stock (Generic, Eq, Show)---- | Everything that actually applies to one frontmatter key for one concept--- type: the profile-scope declaration merged with the type-scope one per--- [ADR 5](docs/adr/5-compile-profile-rules-before-validation.md). Abstract: read--- it with the @fieldRule*@ accessors below, never by pattern matching, so that--- later profile features can extend it without breaking consumers.-data EffectiveFieldRule = EffectiveFieldRule- { presenceClauses :: ![PresenceClause],- description :: !(Maybe Text),- allowedValues :: ![Text],- cardinality :: !Cardinality,- format :: !(Maybe FieldFormat),- elementFields :: !(Maybe (Map Text EffectiveFieldRule)),- objectFields :: !(Maybe (Map Text EffectiveFieldRule)),- reference :: !(Maybe HandleReferenceRule),- path :: !(Maybe PathReferenceRule),- uniqueBy :: !(Maybe Text),- compiledExternalUriPattern :: !(Maybe Regex)- }- deriving stock (Generic)--instance Eq EffectiveFieldRule where- left == right =- left ^. #presenceClauses == right ^. #presenceClauses- && left ^. #description == right ^. #description- && left ^. #allowedValues == right ^. #allowedValues- && left ^. #cardinality == right ^. #cardinality- && left ^. #format == right ^. #format- && left ^. #elementFields == right ^. #elementFields- && left ^. #objectFields == right ^. #objectFields- && left ^. #reference == right ^. #reference- && left ^. #path == right ^. #path- && left ^. #uniqueBy == right ^. #uniqueBy--instance Show EffectiveFieldRule where- show rule =- "EffectiveFieldRule "- <> show- ( rule ^. #presenceClauses,- rule ^. #description,- rule ^. #allowedValues,- rule ^. #cardinality,- rule ^. #format,- rule ^. #elementFields,- rule ^. #objectFields,- rule ^. #reference,- rule ^. #path,- rule ^. #uniqueBy- )---- | The stable lowercase display name for a cardinality: @any@, @scalar@,--- @list@, or @object@. These are the names the CLI prints and the names--- generated profile documentation uses, so a reader who has seen one recognizes--- the other. @object@ is also the word the CLI already prints for an actual--- mapping value, so a cardinality-mismatch message reads coherently.-renderCardinalityName :: Cardinality -> Text-renderCardinalityName = \case- Any -> "any"- Scalar -> "scalar"- List -> "list"- Object -> "object"---- | The stable display name for a named format: @rfc3339-utc@, @date@, @uri@,--- @uri-with-scheme(SCHEME)@, @document-handle(PREFIX)@, @actor@,--- @human-actor@, @integer@, @non-negative-integer@, or @boolean@. As with--- 'renderCardinalityName', this vocabulary is shared between the CLI and--- generated documentation and must not drift between them.-renderFieldFormatName :: FieldFormat -> Text-renderFieldFormatName = \case- Rfc3339Utc -> "rfc3339-utc"- Date -> "date"- Uri -> "uri"- UriWithScheme scheme -> "uri-with-scheme(" <> scheme <> ")"- DocumentHandle prefix -> "document-handle(" <> prefix <> ")"- Actor -> "actor"- HumanActor -> "human-actor"- Integer -> "integer"- NonNegativeInteger -> "non-negative-integer"- Boolean -> "boolean"---- | The presence clauses that govern whether this key must be present, in the--- order the profile declared them. An __empty list means the key is optional__:--- it is fully validated whenever it is present and never reported when absent,--- in any validation mode. A clause with 'RequiredField' is always checked; a--- clause with 'RecommendedField' is checked only under--- 'Okf.Validation.StrictAuthoring'. A clause carrying a 'FieldCondition' applies--- only when that condition holds for the document being checked.-fieldRulePresenceClauses :: EffectiveFieldRule -> [PresenceClause]-fieldRulePresenceClauses rule = rule ^. #presenceClauses---- | Prose documenting the key, merged across profile and type scope with--- type-level prose winning. Purely documentary: it is never checked against a--- document and can never produce a 'ProfileViolation'.-fieldRuleDescription :: EffectiveFieldRule -> Maybe Text-fieldRuleDescription rule = rule ^. #description---- | The closed vocabulary of permitted textual values. An __empty list means--- unconstrained__, not "no value is permitted".-fieldRuleAllowedValues :: EffectiveFieldRule -> [Text]-fieldRuleAllowedValues rule = rule ^. #allowedValues---- | Whether the key must be a single value, a non-empty list, or either.-fieldRuleCardinality :: EffectiveFieldRule -> Cardinality-fieldRuleCardinality rule = rule ^. #cardinality---- | The named textual format constraining present values, if any.-fieldRuleFormat :: EffectiveFieldRule -> Maybe FieldFormat-fieldRuleFormat rule = rule ^. #format---- | The document-reference policy for this key, if any.-fieldRuleReference :: EffectiveFieldRule -> Maybe HandleReferenceRule-fieldRuleReference rule = rule ^. #reference---- | The required scalar member whose values must be unique within this one--- list of records, or 'Nothing' when no list-local key is declared.-fieldRuleUniqueBy :: EffectiveFieldRule -> Maybe Text-fieldRuleUniqueBy rule = rule ^. #uniqueBy---- | The path-valued policy for this key, if any. Distinct from--- 'fieldRuleReference': a handle resolves against the bundle's document-ID--- index, a path against its concept tree. A rule never carries both — compiling--- one that does is a 'PathReferenceWithHandleReference' definition error — so a--- consumer can read whichever is present without disambiguating.------ Unlike 'fieldRuleReference' this can be present on a rule taken from--- 'fieldRuleElementFields' or 'fieldRuleObjectFields', because--- @sources[].resource@ is the field the policy exists for.-fieldRulePath :: EffectiveFieldRule -> Maybe PathReferenceRule-fieldRulePath rule = rule ^. #path---- | Rules for the flat object stored in each element of a list-valued key,--- keyed by nested key name, or 'Nothing' when the key declares no nested shape.--- Nested rules are depth-bounded: a nested rule never itself has element fields,--- so 'fieldRuleElementFields' on a value taken from this map is always--- 'Nothing'.-fieldRuleElementFields :: EffectiveFieldRule -> Maybe (Map Text EffectiveFieldRule)-fieldRuleElementFields rule = rule ^. #elementFields---- | Rules for the members of the mapping stored at this key, keyed by member--- name, or 'Nothing' when the key declares no object shape. Like--- 'fieldRuleElementFields' this is depth-bounded: a value taken from this map--- always has 'Nothing' for both nested accessors. A rule may declare both, which--- means either spelling of the value is accepted and both are checked against--- the same member rules.-fieldRuleObjectFields :: EffectiveFieldRule -> Maybe (Map Text EffectiveFieldRule)-fieldRuleObjectFields rule = rule ^. #objectFields---- | Whether this clause demands the key or merely recommends it.-presenceClauseRequirement :: PresenceClause -> FieldRequirement-presenceClauseRequirement clause = clause ^. #requirement---- | The same-scope predicate gating this clause, or 'Nothing' when it always--- applies.-presenceClauseCondition :: PresenceClause -> Maybe FieldCondition-presenceClauseCondition clause = clause ^. #condition---- | A raw profile whose authoring contradictions have been rejected and whose--- effective profile-plus-type frontmatter rules have been precomputed.-data CompiledProfile = CompiledProfile- { spec :: !ProfileSpec,- baseRules :: !(Map Text EffectiveFieldRule),- rulesByType :: !(Map Text (Map Text EffectiveFieldRule)),- -- | @requireBundleVersion@ already parsed, so 'validateProfileVersion' never- -- re-parses and cannot disagree with what compilation accepted.- requiredBundleVersion :: !(Maybe OkfVersion)- }- deriving stock (Generic, Eq, Show)--compiledProfileSpec :: CompiledProfile -> ProfileSpec-compiledProfileSpec compiled = compiled ^. #spec---- | The minimum OKF version the profile requires its bundle to declare, already--- parsed. 'Nothing' when the profile requires nothing, which is the default and--- the case for almost every profile.-compiledProfileRequiredBundleVersion :: CompiledProfile -> Maybe OkfVersion-compiledProfileRequiredBundleVersion compiled = compiled ^. #requiredBundleVersion---- | The concept @type@ strings the profile declares, in the order the descriptor--- declares them. Declaration order is the author's and is preserved because--- documentation and display should follow it rather than an alphabetical--- reordering.-compiledProfileTypeNames :: CompiledProfile -> [Text]-compiledProfileTypeNames compiled =- [rule ^. #type_ | rule <- compiledProfileSpec compiled ^. #types]---- | The rules that apply to every document, whatever its type, keyed by--- frontmatter key name.-compiledProfileBaseRules :: CompiledProfile -> Map Text EffectiveFieldRule-compiledProfileBaseRules compiled = compiled ^. #baseRules---- | The rules that apply to a document of the given @type@: the profile-scope--- rules merged with that type's own. A type the profile does not declare falls--- back to the profile-scope rules alone, because a profile with--- @allowUnknownTypes = True@ still applies its profile-wide expectations to a--- document whose type it does not recognize.-compiledProfileRulesForType :: CompiledProfile -> Text -> Map Text EffectiveFieldRule-compiledProfileRulesForType = effectiveRulesForType--compileProfile :: ProfileSpec -> Either (NonEmpty ProfileDefinitionError) CompiledProfile-compileProfile rawSpec =- case definitionErrors of- firstError : remainingErrors -> Left (firstError :| remainingErrors)- [] ->- Right- CompiledProfile- { spec = rawSpec,- baseRules,- rulesByType =- Map.fromList- [ (rule ^. #type_, mergeRules baseRules (compileRules (rule ^. #frontmatter)))- | rule <- rawSpec ^. #types- ],- -- Safe here and only here: 'requiredBundleVersionErrors' is one of- -- the checks 'definitionErrors' collects, so reaching this branch- -- means the value parsed.- requiredBundleVersion = rawSpec ^. #requireBundleVersion >>= parseOkfVersion- }- where- baseRules = compileRules (rawSpec ^. #frontmatter)- typeNames = map (^. #type_) (rawSpec ^. #types)- definitionErrors =- List.sortOn definitionErrorKey $- map DuplicateTypeRule (duplicates typeNames)- <> scopeErrors Nothing (rawSpec ^. #frontmatter)- <> concat- [ scopeErrors (Just (rule ^. #type_)) (rule ^. #frontmatter)- | rule <- rawSpec ^. #types- ]- <> vocabularyErrors- <> cardinalityErrors- <> nestedCardinalityErrors- <> objectCardinalityErrors- <> formatParameterErrors- <> conflictingFormatErrors- <> conditionDefinitionErrors- <> referenceDefinitionErrors- <> uniquenessDefinitionErrors- <> versionErrors- <> requiredBundleVersionErrors-- definitionErrorKey = \case- DuplicateTypeRule ctype -> (1 :: Int, ctype, 0 :: Int, "", 0 :: Int)- DuplicateFieldRule scope listName key ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, listKey listName, key, 0)- ConflictingFieldRequirement scope key ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 2, key, 0)- UnsatisfiableVocabulary scope key _ _ ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 3, key, 0)- ConflictingCardinality scope key _ _ ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 4, key, 0)- ElementFieldsRequireList scope path cardinality ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 4, renderFieldPathKey path, fromEnum (cardinality == Scalar))- InvalidFormatParameter path fieldFormat parameter ->- (2, renderFieldPathKey path, 5, Text.pack (show fieldFormat), Text.length parameter)- ConflictingFieldFormat path profileFormat typeFormat ->- (2, renderFieldPathKey path, 6, Text.pack (show profileFormat), fromEnum (profileFormat == typeFormat))- EmptyConditionValues scope target source -> conditionErrorKey scope target source 7- ConditionFieldNotDeclared scope target source -> conditionErrorKey scope target source 8- ConditionFieldNotScalar scope target source cardinality ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 9, renderFieldPathKey target <> ":" <> renderFieldPathKey source, fromEnum (cardinality == Scalar))- ConditionFieldOpenVocabulary scope target source -> conditionErrorKey scope target source 10- ConditionFieldHasUnreachableValues scope target source _ _ -> conditionErrorKey scope target source 11- SelfConditionalField scope target ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 12, renderFieldPathKey target, 0)- InvalidReferencePrefix scope target prefix -> referenceErrorKey scope target 13 prefix- ReferencePrefixNotDeclared scope target prefix -> referenceErrorKey scope target 14 prefix- ReferenceRequiresIdField scope target -> referenceErrorKey scope target 15 ""- InvalidExternalReferenceScheme scope target scheme -> referenceErrorKey scope target 16 scheme- ConflictingReferencePrefix ctype target profilePrefix typePrefix ->- (1, ctype, 17, renderFieldPathKey target <> ":" <> profilePrefix, Text.length typePrefix)- ReferenceWithFormat scope target fieldFormat -> referenceErrorKey scope target 18 (Text.pack (show fieldFormat))- OptionalFieldWithCondition scope target ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 19, renderFieldPathKey target, 0)- ObjectFieldsRequireObjectShape scope fieldPath cardinality ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 20, renderFieldPathKey fieldPath, fromEnum (cardinality == Scalar))- PathReferenceWithHandleReference scope target -> referenceErrorKey scope target 21 ""- InvalidExternalUriPattern scope target patternText detail ->- referenceErrorKey scope target 22 (patternText <> ":" <> detail)- ConflictingExternalUriPatterns ctype target profilePattern typePattern ->- (1, ctype, 23, renderFieldPathKey target <> ":" <> profilePattern, Text.length typePattern)- UniqueByRequiresElementFields scope target key -> uniqueErrorKey scope target 24 key- UniqueByFieldNotDeclared scope target -> uniqueErrorKey scope target 25 ""- UniqueByFieldNotUnconditionallyRequired scope target -> uniqueErrorKey scope target 26 ""- UniqueByFieldNotScalar scope target cardinality -> uniqueErrorKey scope target 27 (Text.pack (show cardinality))- ConflictingUniqueBy ctype target profileKey typeKey ->- (1, ctype, 28, renderFieldPathKey target <> ":" <> profileKey, Text.length typeKey)- -- The two version-parse errors are profile-wide rather than scoped, and- -- rank below every scope rank: if the declared version is unreadable, every- -- version-derived error below is downstream noise and the reader should see- -- the cause first. Nothing constrains the first component to be- -- non-negative.- InvalidProfileOkfVersion rawVersion -> (-1, rawVersion, 0, "", 0)- ProfileOkfVersionNotUnderstood rawVersion -> (-1, rawVersion, 1, "", 0)- InvalidRequiredBundleVersion rawVersion -> (-1, rawVersion, 2, "", 0)- FieldSupersededInOkfVersion scope path _declared supersededIn ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 30, renderFieldPathKey path <> ":" <> supersededIn, 0)- FormatRequiresOkfVersion scope path fieldFormat _declared introducedIn ->- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, 31, renderFieldPathKey path <> ":" <> introducedIn, Text.length (Text.pack (show fieldFormat)))-- scopeKey Nothing = (0, "")- scopeKey (Just ctype) = (1, ctype)- listKey "required" = 0- listKey _ = 1- conditionErrorKey scope target source rank =- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, rank, renderFieldPathKey target <> ":" <> renderFieldPathKey source, 0)- referenceErrorKey scope target rank detail =- let (scopeRank, typeName) = scopeKey scope- in (scopeRank, typeName, rank, renderFieldPathKey target <> ":" <> detail, 0)- uniqueErrorKey = referenceErrorKey-- scopeErrors scope FrontmatterRules {required, recommended, optional} =- [DuplicateFieldRule scope "required" key | key <- duplicates (map (^. #field) required)]- <> [DuplicateFieldRule scope "recommended" key | key <- duplicates (map (^. #field) recommended)]- <> [DuplicateFieldRule scope "optional" key | key <- duplicates (map (^. #field) optional)]- <> [ ConflictingFieldRequirement scope key- | key <- presenceListCollisions (map (^. #field) required) (map (^. #field) recommended) (map (^. #field) optional)- ]- <> concatMap (nestedScopeErrors scope) (required <> recommended <> optional)-- nestedScopeErrors scope parentRule =- concatMap oneNestedRuleSet (declaredNestedRuleSets parentRule)- where- oneNestedRuleSet NestedRules {required, recommended, optional} =- let qualify key = parentRule ^. #field <> "." <> key- in [DuplicateFieldRule scope "nested required" (qualify key) | key <- duplicates (map (^. #field) required)]- <> [DuplicateFieldRule scope "nested recommended" (qualify key) | key <- duplicates (map (^. #field) recommended)]- <> [DuplicateFieldRule scope "nested optional" (qualify key) | key <- duplicates (map (^. #field) optional)]- <> [ ConflictingFieldRequirement scope (qualify key)- | key <- presenceListCollisions (map (^. #field) required) (map (^. #field) recommended) (map (^. #field) optional)- ]-- -- One key classified by more than one presence list at a single scope. The- -- profile cannot say both "always check for this" and "never check for- -- this", so any pairing is a definition error rather than a precedence rule.- presenceListCollisions requiredKeys recommendedKeys optionalKeys =- List.nub . List.sort . concat $- [ List.intersect requiredKeys recommendedKeys,- List.intersect requiredKeys optionalKeys,- List.intersect recommendedKeys optionalKeys- ]-- duplicates = mapMaybe duplicateHead . List.group . List.sort- duplicateHead (candidate : _ : _) = Just candidate- duplicateHead _ = Nothing-- vocabularyErrors =- [ UnsatisfiableVocabulary (Just (rule ^. #type_)) key profileValues typeValues- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- let profileValues = profileRule ^. #allowedValues- typeValues = typeRule ^. #allowedValues,- not (null profileValues),- not (null typeValues),- null (mergeVocabulary profileValues typeValues)- ]- <> [ UnsatisfiableVocabulary (Just (rule ^. #type_)) (parentKey <> "." <> nestedKey) profileValues typeValues- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,- (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),- let profileValues = profileNestedRule ^. #allowedValues- typeValues = typeNestedRule ^. #allowedValues,- not (null profileValues),- not (null typeValues),- null (mergeVocabulary profileValues typeValues)- ]-- cardinalityErrors =- [ ConflictingCardinality (Just (rule ^. #type_)) key profileCardinality typeCardinality- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- let profileCardinality = profileRule ^. #cardinality- typeCardinality = typeRule ^. #cardinality,- profileCardinality /= Any,- typeCardinality /= Any,- profileCardinality /= typeCardinality- ]- <> [ ConflictingCardinality (Just (rule ^. #type_)) (parentKey <> "." <> nestedKey) profileCardinality typeCardinality- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,- (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),- let profileCardinality = profileNestedRule ^. #cardinality- typeCardinality = typeNestedRule ^. #cardinality,- profileCardinality /= Any,- typeCardinality /= Any,- profileCardinality /= typeCardinality- ]-- nestedCardinalityErrors =- [ ElementFieldsRequireList scope (topLevelFieldPath (fieldRule ^. #field)) Scalar- | (scope, rules) <- scopedFrontmatterRules,- fieldRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- isJust (fieldRule ^. #elementFields),- fieldRule ^. #cardinality == Scalar- ]-- -- The mirror image: @objectFields@ says the value is a mapping, and neither- -- an explicit @scalar@ nor an explicit @list@ can be one.- objectCardinalityErrors =- [ ObjectFieldsRequireObjectShape scope (topLevelFieldPath (fieldRule ^. #field)) declared- | (scope, rules) <- scopedFrontmatterRules,- fieldRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- isJust (fieldRule ^. #objectFields),- let declared = fieldRule ^. #cardinality,- declared == Scalar || declared == List- ]-- scopedFrontmatterRules =- (Nothing, rawSpec ^. #frontmatter)- : [(Just (rule ^. #type_), rule ^. #frontmatter) | rule <- rawSpec ^. #types]-- -- The nested rule sets one raw rule declares: its list-element rules, its- -- object-member rules, or both. Deduplicated because @mk.recordOrList@- -- deliberately declares the same rules under both names, and a definition- -- error names a path such as @verified.by@ that does not distinguish the two- -- — so without this the same incoherence would be reported twice.- declaredNestedRuleSets rawRule =- List.nub (catMaybes [rawRule ^. #elementFields, rawRule ^. #objectFields])-- -- The same idea across two scopes: the nested maps a profile-scope rule and- -- a type-scope rule both declare, paired shape with matching shape. Pairing- -- an element map against an object map would compare rules that never meet.- pairedNestedRuleMaps profileRule typeRule =- List.nub- [ (profileNested, typeNested)- | nestedMap <- [(^. #elementFields), (^. #objectFields)],- Just profileNested <- [nestedMap profileRule],- Just typeNested <- [nestedMap typeRule]- ]-- formatParameterErrors =- [ InvalidFormatParameter (topLevelFieldPath (rule ^. #field)) fieldFormat parameter- | rules <- (rawSpec ^. #frontmatter) : map (^. #frontmatter) (rawSpec ^. #types),- rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- Just (fieldFormat, parameter) <- [invalidFormatParameter =<< rule ^. #format]- ]- <> [ InvalidFormatParameter (nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field)) fieldFormat parameter- | rules <- (rawSpec ^. #frontmatter) : map (^. #frontmatter) (rawSpec ^. #types),- rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- nestedRules <- declaredNestedRuleSets rule,- nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,- Just (fieldFormat, parameter) <- [invalidFormatParameter =<< nestedRule ^. #format]- ]-- conflictingFormatErrors =- [ ConflictingFieldFormat (topLevelFieldPath key) profileFormat typeFormat- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- Just profileFormat <- [profileRule ^. #format],- Just typeFormat <- [typeRule ^. #format],- mergeFieldFormat (Just profileFormat) (Just typeFormat) == Nothing- ]- <> [ ConflictingFieldFormat (nestedDefinitionPath parentKey nestedKey) profileFormat typeFormat- | rule <- rawSpec ^. #types,- let typeRules = compileRules (rule ^. #frontmatter),- (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,- (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),- Just profileFormat <- [profileNestedRule ^. #format],- Just typeFormat <- [typeNestedRule ^. #format],- mergeFieldFormat (Just profileFormat) (Just typeFormat) == Nothing- ]-- conditionDefinitionErrors =- conditionErrors Nothing Nothing (rawSpec ^. #frontmatter) baseRules- <> concat- [ let typeRules = compileRules (rule ^. #frontmatter)- mergedRules = mergeRules baseRules typeRules- in conditionErrors (Just (rule ^. #type_)) Nothing (rule ^. #frontmatter) mergedRules- | rule <- rawSpec ^. #types- ]-- conditionErrors scope parent rules effectiveRules =- concatMap (fieldConditionErrors scope parent effectiveRules) (rules ^. #required <> rules ^. #recommended)- <> [ deadCondition scope parent (rawRule ^. #field)- | rawRule <- rules ^. #optional,- isJust (rawRule ^. #when)- ]- <> List.nub- ( concat- [ concatMap- (fieldConditionErrors scope (Just (rawRule ^. #field)) effectiveNestedRules)- (nestedRules ^. #required <> nestedRules ^. #recommended)- <> [ deadCondition scope (Just (rawRule ^. #field)) (nestedRule ^. #field)- | nestedRule <- nestedRules ^. #optional,- isJust (nestedRule ^. #when)- ]- | rawRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- let effectiveRule = Map.lookup (rawRule ^. #field) effectiveRules,- (nestedRules, effectiveNestedRules) <-- catMaybes- [ (,) <$> (rawRule ^. #elementFields) <*> (effectiveRule >>= (^. #elementFields)),- (,) <$> (rawRule ^. #objectFields) <*> (effectiveRule >>= (^. #objectFields))- ]- ]- )-- -- A condition gates presence, and an optional rule has no presence clause to- -- gate, so the pairing is dead in the descriptor rather than a weaker rule.- deadCondition scope parent key =- OptionalFieldWithCondition- scope- (maybe (topLevelFieldPath key) (\parentKey -> nestedDefinitionPath parentKey key) parent)-- fieldConditionErrors scope parent effectiveRules rawRule =- case rawRule ^. #when of- Nothing -> []- Just FieldCondition {field = sourceKey, hasValue = conditionValues} ->- let targetKey = rawRule ^. #field- targetPath = maybe (topLevelFieldPath targetKey) (\parentKey -> nestedDefinitionPath parentKey targetKey) parent- sourcePath = maybe (topLevelFieldPath sourceKey) (\parentKey -> nestedDefinitionPath parentKey sourceKey) parent- normalizedValues = deduplicate conditionValues- sourceErrors =- case Map.lookup sourceKey effectiveRules of- Nothing -> [ConditionFieldNotDeclared scope targetPath sourcePath]- Just sourceRule ->- [ ConditionFieldNotScalar scope targetPath sourcePath (sourceRule ^. #cardinality)- | sourceRule ^. #cardinality /= Scalar- ]- <> [ ConditionFieldOpenVocabulary scope targetPath sourcePath- | null (sourceRule ^. #allowedValues)- ]- <> let unreachable = filter (`notElem` sourceRule ^. #allowedValues) normalizedValues- in [ ConditionFieldHasUnreachableValues scope targetPath sourcePath unreachable (sourceRule ^. #allowedValues)- | not (null (sourceRule ^. #allowedValues)),- not (null unreachable)- ]- in [EmptyConditionValues scope targetPath sourcePath | null normalizedValues]- <> [SelfConditionalField scope targetPath | targetKey == sourceKey]- <> sourceErrors-- referenceDefinitionErrors =- List.nub $- concatMap rawReferenceErrors scopedRules- <> concatMap mergedReferenceErrors (rawSpec ^. #types)- where- declaredPrefixes = mapMaybe (^. #idPrefix) (rawSpec ^. #types)- scopedRules =- (Nothing, rawSpec ^. #frontmatter)- : [(Just (rule ^. #type_), rule ^. #frontmatter) | rule <- rawSpec ^. #types]-- rawReferenceErrors (scope, rules) =- concatMap topLevelPolicyErrors topLevelRules- <> [ nestedError- | rule <- topLevelRules,- nestedRules <- declaredNestedRuleSets rule,- nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,- nestedError <- policyErrors scope (nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field)) (nestedRule ^. #format) (nestedRule ^. #reference) (nestedRule ^. #path)- ]- where- topLevelRules = rules ^. #required <> rules ^. #recommended <> rules ^. #optional- topLevelPolicyErrors rule =- policyErrors scope (topLevelFieldPath (rule ^. #field)) (rule ^. #format) (rule ^. #reference) (rule ^. #path)-- policyErrors scope path declaredFormat handlePolicy pathPolicy =- referencePolicyErrors scope path declaredFormat handlePolicy- <> pathPolicyErrors scope path declaredFormat handlePolicy pathPolicy-- referencePolicyErrors scope path declaredFormat = \case- Nothing -> []- Just policy ->- let prefix = policy ^. #localPrefix- schemes = deduplicateSchemes (policy ^. #externalUriSchemes)- in [InvalidReferencePrefix scope path prefix | not (validDocumentHandlePrefix prefix)]- <> [ReferencePrefixNotDeclared scope path prefix | prefix `notElem` declaredPrefixes]- <> [ReferenceRequiresIdField scope path | isNothing (rawSpec ^. #idField)]- <> [InvalidExternalReferenceScheme scope path scheme | scheme <- schemes, not (validUriScheme scheme)]- <> [ReferenceWithFormat scope path fieldFormat | Just fieldFormat <- [declaredFormat]]- <> [ InvalidExternalUriPattern scope path patternText detail- | Just patternText <- [policy ^. #externalUriPattern],- Left detail <- [compileExternalUriPattern patternText]- ]-- -- The three ways a path policy can be incoherent on its own. Two reuse- -- the handle-reference constructors because the claim is identical: a- -- scheme that is not a legal URI scheme, and a structural interpretation- -- of a value paired with a textual format that would be checked against- -- the same text. The third is genuinely new — a value cannot be resolved- -- as both a @PREFIX-N@ handle and a §6.2 path — and cannot arise at- -- nested scope, where 'NestedFieldRule' carries no handle policy.- pathPolicyErrors scope path declaredFormat handlePolicy = \case- Nothing -> []- Just policy ->- [ InvalidExternalReferenceScheme scope path scheme- | scheme <- deduplicateSchemes (policy ^. #externalUriSchemes),- not (validUriScheme scheme)- ]- <> [ReferenceWithFormat scope path fieldFormat | Just fieldFormat <- [declaredFormat]]- <> [PathReferenceWithHandleReference scope path | isJust handlePolicy]-- mergedReferenceErrors typeRule =- [ ConflictingReferencePrefix (typeRule ^. #type_) path (profilePolicy ^. #localPrefix) (typePolicy ^. #localPrefix)- | (path, profileRule, typeFieldRule) <- pairedRules typeRule,- Just profilePolicy <- [profileRule ^. #reference],- Just typePolicy <- [typeFieldRule ^. #reference],- profilePolicy ^. #localPrefix /= typePolicy ^. #localPrefix- ]- <> [ ConflictingExternalUriPatterns (typeRule ^. #type_) path profilePattern typePattern- | (path, profileRule, typeFieldRule) <- pairedRules typeRule,- Just profilePattern <- [profileRule ^. #reference >>= (^. #externalUriPattern)],- Just typePattern <- [typeFieldRule ^. #reference >>= (^. #externalUriPattern)],- profilePattern /= typePattern- ]- <> [ ReferenceWithFormat (Just (typeRule ^. #type_)) path fieldFormat- | (path, profileRule, typeFieldRule) <- pairedRules typeRule,- (referenceRule, formatRule) <- [(profileRule, typeFieldRule), (typeFieldRule, profileRule)],- isJust (referenceRule ^. #reference),- Just fieldFormat <- [formatRule ^. #format]- ]- <> [ PathReferenceWithHandleReference (Just (typeRule ^. #type_)) path- | (path, profileRule, typeFieldRule) <- pairedRules typeRule,- (referenceRule, pathRule) <- [(profileRule, typeFieldRule), (typeFieldRule, profileRule)],- isJust (referenceRule ^. #reference),- isJust (pathRule ^. #path)- ]-- pairedRules typeRule =- [ (topLevelFieldPath key, profileRule, typeFieldRule)- | let typeFields = compileRules (typeRule ^. #frontmatter),- (key, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeFields)- ]- <> [ (nestedDefinitionPath parentKey nestedKey, profileNestedRule, typeNestedRule)- | let typeFields = compileRules (typeRule ^. #frontmatter),- (parentKey, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeFields),- (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeFieldRule,- (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested)- ]-- uniquenessDefinitionErrors =- concatMap (validateDeclarations Nothing baseRules) [rawSpec ^. #frontmatter]- <> concat- [ let typeRules = compileRules (typeRule ^. #frontmatter)- in validateDeclarations (Just (typeRule ^. #type_)) (mergeRules baseRules typeRules) (typeRule ^. #frontmatter)- | typeRule <- rawSpec ^. #types- ]- <> [ ConflictingUniqueBy (typeRule ^. #type_) (topLevelFieldPath key) profileKey typeKey- | typeRule <- rawSpec ^. #types,- let typeRules = compileRules (typeRule ^. #frontmatter),- (key, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),- Just profileKey <- [profileRule ^. #uniqueBy],- Just typeKey <- [typeFieldRule ^. #uniqueBy],- profileKey /= typeKey- ]- where- validateDeclarations scope effectiveRules rules =- concatMap (validateDeclaration scope effectiveRules) (rules ^. #required <> rules ^. #recommended <> rules ^. #optional)-- validateDeclaration scope effectiveRules rawRule =- case rawRule ^. #uniqueBy of- Nothing -> []- Just requestedKey ->- let parentPath = topLevelFieldPath (rawRule ^. #field)- memberPath = nestedDefinitionPath (rawRule ^. #field) requestedKey- in case Map.lookup (rawRule ^. #field) effectiveRules >>= (^. #elementFields) of- Nothing -> [UniqueByRequiresElementFields scope parentPath requestedKey]- Just memberRules ->- case Map.lookup requestedKey memberRules of- Nothing -> [UniqueByFieldNotDeclared scope memberPath]- Just memberRule ->- [ UniqueByFieldNotUnconditionallyRequired scope memberPath- | not (any unconditionalRequired (memberRule ^. #presenceClauses))- ]- <> [ UniqueByFieldNotScalar scope memberPath (memberRule ^. #cardinality)- | memberRule ^. #cardinality /= Scalar- ]-- unconditionalRequired clause =- clause ^. #requirement == RequiredField && isNothing (clause ^. #condition)-- -- Only a value okf cannot parse is rejected. An unknown /major/ is- -- deliberately accepted, unlike in @okfVersion@: there the profile is asking- -- okf to interpret rules it may not understand, while here it is stating a- -- minimum that a bundle's own declaration is compared against, which stays- -- meaningful whatever the major is.- requiredBundleVersionErrors =- case rawSpec ^. #requireBundleVersion of- Just rawVersion- | isNothing (parseOkfVersion rawVersion) -> [InvalidRequiredBundleVersion rawVersion]- _ -> []-- versionErrors =- case effectiveProfileVersion (rawSpec ^. #okfVersion) of- Left versionError -> [versionError]- Right effectiveVersion ->- let atLeastV02 = effectiveVersion >= okfVersion02- v02Text = renderOkfVersion okfVersion02- -- The /effective/ version, not the raw declared string: a profile- -- declaring 0.9 is checked as 0.2 and its diagnostics should say so- -- rather than repeating a number okf did not act on.- declaredText = renderOkfVersion effectiveVersion- in -- A key v0.2 superseded, demanded by a profile declaring v0.2 or- -- later. Deliberately /not/ checked in the optional list: a team- -- migrating a corpus wants @generated@ required and @timestamp@- -- tolerated but not demanded, and the optional list says exactly- -- that. This is the one check whose answer depends on which presence- -- list a rule came from, so it reads the raw lists rather than the- -- compiled map, which erases the distinction into presence clauses.- [ FieldSupersededInOkfVersion scope (topLevelFieldPath (rule ^. #field)) declaredText v02Text- | atLeastV02,- (scope, rules) <- scopedFrontmatterRules,- rule <- rules ^. #required <> rules ^. #recommended,- rule ^. #field `elem` fieldsSupersededInV02- ]- -- The actor formats encode the specification §7 convention that- -- v0.2 introduced. A format is an okf descriptor feature rather- -- than a key name, so unlike the mirror case below it has no- -- house-convention reading: @FieldFormat.Actor@ /is/ §7.- <> [ FormatRequiresOkfVersion scope path fieldFormat declaredText v02Text- | not atLeastV02,- (scope, path, fieldFormat) <- scopedFormats,- fieldFormat `elem` [Actor, HumanActor]- ]-- -- There is deliberately no mirror check, "a profile declaring v0.1 names a- -- key v0.2 introduced", which is why 'fieldsIntroducedInV02' is unused here.- -- A profile key /name/ does not imply the OKF core key of that name: per- -- docs/adr/1-profile-declared-document-ids.md, constraining keys the core- -- format does not own is what profiles are for, and @status@, @sources@, and- -- @verified@ are ordinary words teams were already using as house conventions- -- before v0.2 claimed them. Such a check is both retroactive and ambiguous,- -- which docs/adr/11-growing-the-profile-descriptor-language.md forbids.-- -- Every declared format paired with the path it sits at, top-level first and- -- then nested. Only the format checks descend: 'fieldsIntroducedInV02' and- -- 'fieldsSupersededInV02' name /concept-level/ frontmatter keys, so matching- -- them against a member of a record would reject a profile whose element- -- happens to be called @status@ for having a v0.2 key it does not have.- scopedFormats =- [ (scope, topLevelFieldPath (rule ^. #field), fieldFormat)- | (scope, rules) <- scopedFrontmatterRules,- rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- Just fieldFormat <- [rule ^. #format]- ]- <> List.nub- [ (scope, nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field), fieldFormat)- | (scope, rules) <- scopedFrontmatterRules,- rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,- nestedRules <- declaredNestedRuleSets rule,- nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,- Just fieldFormat <- [nestedRule ^. #format]- ]-- renderFieldPathKey (FieldPath (firstSegment :| remainingSegments)) =- Text.intercalate "." (map renderSegment (firstSegment : remainingSegments))- renderSegment (FieldName name) = name- renderSegment (ArrayIndex elementIndex) = Text.pack (show elementIndex)---- | The OKF version that introduced the v0.2 frontmatter families and the actor--- convention. Written out rather than reusing 'supportedOkfVersion', which--- happens to equal it today: one is "what okf implements" and the other is "what--- introduced these keys", and they will diverge at the next minor bump.-okfVersion02 :: OkfVersion-okfVersion02 = OkfVersion {okfVersionMajor = 0, okfVersionMinor = 2}---- | The OKF version a profile's rules are checked against, or why okf cannot--- tell.------ A higher /minor/ within a known major is clamped, mirroring--- 'Okf.Validation.versionGate': specification §12 defines a minor bump as--- backward-compatible additions, so every rule a v0.3 profile can express is one--- okf already understands.------ An unknown /major/ is deliberately an error, where the bundle side reads--- best-effort — this is a considered divergence, not an oversight. §12's--- best-effort instruction is about bundles, which may come from a third party--- okf cannot ask. A profile is not a document okf is asked to read; it is an--- instruction to okf about what to check, written by an author who is present.--- Silently ignoring an instruction okf cannot interpret is worse than saying so.--- See @docs\/adr\/10-okf-version-declaration-and-best-effort-reading.md@.-effectiveProfileVersion :: Text -> Either ProfileDefinitionError OkfVersion-effectiveProfileVersion rawVersion =- case parseOkfVersion rawVersion of- Nothing -> Left (InvalidProfileOkfVersion rawVersion)- Just declared- | okfVersionMajor declared == okfVersionMajor supportedOkfVersion ->- Right (min declared supportedOkfVersion)- | otherwise -> Left (ProfileOkfVersionNotUnderstood rawVersion)--compileRules :: FrontmatterRules -> Map Text EffectiveFieldRule-compileRules FrontmatterRules {required, recommended, optional} =- Map.fromList- ( [ (rule ^. #field, compileFieldRule RequiredField rule)- | rule <- required- ]- <> [ (rule ^. #field, compileFieldRule RecommendedField rule)- | rule <- recommended- ]- <> [ (rule ^. #field, compileOptionalFieldRule rule)- | rule <- optional- ]- )---- | Compile a rule the profile documents but never demands. It carries no--- presence clause, so 'applicablePresenceClause' can never find one to report in--- either validation mode, while every value check still runs from the--- present-value branch. This is also the value constraints a required or--- recommended rule is built from.-compileOptionalFieldRule :: FieldRule -> EffectiveFieldRule-compileOptionalFieldRule rule =- EffectiveFieldRule- { presenceClauses = [],- description = rule ^. #description,- allowedValues = deduplicate (rule ^. #allowedValues),- cardinality =- -- Declaring a nested shape and no explicit cardinality refines what the- -- value may be, because the shape only makes sense against one. A rule- -- declaring both shapes stays 'Any', which is what lets either spelling- -- of the OKF v0.2 @verified@ key satisfy it.- case (rule ^. #objectFields, rule ^. #elementFields, rule ^. #cardinality) of- (Just _, Just _, Any) -> Any- (Just _, Nothing, Any) -> Object- (Nothing, Just _, Any) -> List- (_, _, Any) -> refineCardinalityForFormat (rule ^. #format)- (_, _, declared) -> declared,- format = rule ^. #format,- elementFields = compileNestedRules <$> rule ^. #elementFields,- objectFields = compileNestedRules <$> rule ^. #objectFields,- reference = compiledReference,- path = compilePathRule <$> rule ^. #path,- uniqueBy = rule ^. #uniqueBy,- compiledExternalUriPattern = compileReferenceMatcher compiledReference- }- where- compiledReference = compileReferenceRule <$> rule ^. #reference---- | The cardinality a rule with no declared one takes from its format.------ A non-textual format implies a scalar, and without this the rule would be--- useless: the 'Any' cardinality routes presence through 'legacyValueIsPresent',--- which counts only non-empty text and non-empty arrays, so a @usage_count:--- 5000@ is reported /missing/ before its value is ever examined. Refining here--- rather than widening 'legacyValueIsPresent' keeps the meaning of every--- descriptor that already exists — in particular a key whose value is @false@--- keeps being reported as missing when no rule says otherwise.------ An explicitly declared cardinality always wins, including @list@: a list of--- integers is a coherent thing to demand, so pairing a numeric format with--- 'List' is left alone rather than made an error.-refineCardinalityForFormat :: Maybe FieldFormat -> Cardinality-refineCardinalityForFormat = \case- Just Integer -> Scalar- Just NonNegativeInteger -> Scalar- Just Boolean -> Scalar- _ -> Any--compileFieldRule :: FieldRequirement -> FieldRule -> EffectiveFieldRule-compileFieldRule requirement rule =- (compileOptionalFieldRule rule)- { presenceClauses = [PresenceClause requirement (compileCondition <$> rule ^. #when)]- }--compileNestedRules :: NestedRules -> Map Text EffectiveFieldRule-compileNestedRules NestedRules {required, recommended, optional} =- Map.fromList- ( [ (rule ^. #field, compileNestedFieldRule RequiredField rule)- | rule <- required- ]- <> [ (rule ^. #field, compileNestedFieldRule RecommendedField rule)- | rule <- recommended- ]- <> [ (rule ^. #field, compileOptionalNestedFieldRule rule)- | rule <- optional- ]- )---- | The nested counterpart of 'compileOptionalFieldRule'.-compileOptionalNestedFieldRule :: NestedFieldRule -> EffectiveFieldRule-compileOptionalNestedFieldRule rule =- EffectiveFieldRule- { presenceClauses = [],- description = rule ^. #description,- allowedValues = deduplicate (rule ^. #allowedValues),- cardinality =- case rule ^. #cardinality of- Any -> refineCardinalityForFormat (rule ^. #format)- declared -> declared,- format = rule ^. #format,- elementFields = Nothing,- -- Nested rules stay depth-bounded: 'NestedFieldRule' has no object member,- -- so a profile cannot constrain @sources[0].usage_window.from@.- objectFields = Nothing,- reference = compiledReference,- -- But it does carry a path policy, which is the point of the member —- -- @sources[].resource@ is only reachable here.- path = compilePathRule <$> rule ^. #path,- uniqueBy = Nothing,- compiledExternalUriPattern = compileReferenceMatcher compiledReference- }- where- compiledReference = compileReferenceRule <$> rule ^. #reference--compileNestedFieldRule :: FieldRequirement -> NestedFieldRule -> EffectiveFieldRule-compileNestedFieldRule requirement rule =- (compileOptionalNestedFieldRule rule)- { presenceClauses = [PresenceClause requirement (compileCondition <$> rule ^. #when)]- }--mergeRules :: Map Text EffectiveFieldRule -> Map Text EffectiveFieldRule -> Map Text EffectiveFieldRule-mergeRules = Map.unionWith mergeEffectiveFieldRule--mergeEffectiveFieldRule :: EffectiveFieldRule -> EffectiveFieldRule -> EffectiveFieldRule-mergeEffectiveFieldRule profileRule typeRule =- EffectiveFieldRule- { presenceClauses = profileRule ^. #presenceClauses <> typeRule ^. #presenceClauses,- description = typeRule ^. #description <|> profileRule ^. #description,- allowedValues = mergeVocabulary (profileRule ^. #allowedValues) (typeRule ^. #allowedValues),- cardinality = mergeCardinality (profileRule ^. #cardinality) (typeRule ^. #cardinality),- format = fromMaybe (profileRule ^. #format) (mergeFieldFormat (profileRule ^. #format) (typeRule ^. #format)),- elementFields = mergeNestedRuleMaps (profileRule ^. #elementFields) (typeRule ^. #elementFields),- objectFields = mergeNestedRuleMaps (profileRule ^. #objectFields) (typeRule ^. #objectFields),- reference = mergedReference,- path = mergePathRule (profileRule ^. #path) (typeRule ^. #path),- uniqueBy = fromMaybe (profileRule ^. #uniqueBy) (mergeUniqueBy (profileRule ^. #uniqueBy) (typeRule ^. #uniqueBy)),- compiledExternalUriPattern = compileReferenceMatcher mergedReference- }- where- mergedReference = fromMaybe (profileRule ^. #reference) (mergeReferenceRule (profileRule ^. #reference) (typeRule ^. #reference))---- | Normalize a declared condition for storage in a 'PresenceClause': the shape--- is unchanged, but the accepted-value list is deduplicated so that a clause--- reported in a 'ProfileViolation' does not repeat a value the author wrote--- twice.-compileCondition :: FieldCondition -> FieldCondition-compileCondition rawCondition =- FieldCondition- { field = rawCondition ^. #field,- hasValue = deduplicate (rawCondition ^. #hasValue)- }--compileReferenceRule :: HandleReferenceRule -> HandleReferenceRule-compileReferenceRule policy =- HandleReferenceRule- { localPrefix = policy ^. #localPrefix,- externalUriSchemes = map Text.toCaseFold (deduplicateSchemes (policy ^. #externalUriSchemes)),- allowSelf = policy ^. #allowSelf,- allowLocal = policy ^. #allowLocal,- externalUriPattern = policy ^. #externalUriPattern- }--compileExternalUriPattern :: Text -> Either Text Regex-compileExternalUriPattern patternText =- first Text.pack (Regex.Text.compile defaultCompOpt defaultExecOpt patternText)--compileReferenceMatcher :: Maybe HandleReferenceRule -> Maybe Regex-compileReferenceMatcher policy = do- patternText <- policy >>= (^. #externalUriPattern)- either (const Nothing) Just (compileExternalUriPattern patternText)--matchesWholeExternalUriPattern :: Regex -> Text -> Bool-matchesWholeExternalUriPattern regex value =- case Regex.Text.regexec regex value of- Right (Just (before, _matched, after, _groups)) -> Text.null before && Text.null after- _ -> False--compilePathRule :: PathReferenceRule -> PathReferenceRule-compilePathRule policy =- PathReferenceRule- { externalUriSchemes = map Text.toCaseFold (deduplicateSchemes (policy ^. #externalUriSchemes)),- allowSelf = policy ^. #allowSelf- }---- | Combine a profile-scope path policy with a type-scope one: intersect the--- permitted schemes and require both scopes to allow self-reference.------ Unlike 'mergeReferenceRule' this is total and needs no @Maybe@-of-@Maybe@--- result, because a path policy has no @localPrefix@ — the one thing two--- handle policies can flatly disagree about. Narrowing to the intersection is--- the same direction 'mergeVocabulary' takes: a type rule tightens the--- profile-wide rule and never loosens it.-mergePathRule :: Maybe PathReferenceRule -> Maybe PathReferenceRule -> Maybe PathReferenceRule-mergePathRule Nothing typePolicy = typePolicy-mergePathRule profilePolicy Nothing = profilePolicy-mergePathRule (Just profilePolicy) (Just typePolicy) =- Just- PathReferenceRule- { externalUriSchemes =- filter- (`Set.member` Set.fromList (typePolicy ^. #externalUriSchemes))- (profilePolicy ^. #externalUriSchemes),- allowSelf = profilePolicy ^. #allowSelf && typePolicy ^. #allowSelf- }---- | Merge one scope's map of member rules with another's. Used for both--- @elementFields@ and @objectFields@; it was named for the former until the--- latter existed and is a plain union-with-merge either way.-mergeNestedRuleMaps :: Maybe (Map Text EffectiveFieldRule) -> Maybe (Map Text EffectiveFieldRule) -> Maybe (Map Text EffectiveFieldRule)-mergeNestedRuleMaps Nothing typeRules = typeRules-mergeNestedRuleMaps profileRules Nothing = profileRules-mergeNestedRuleMaps (Just profileRules) (Just typeRules) = Just (Map.unionWith mergeEffectiveFieldRule profileRules typeRules)--mergeReferenceRule :: Maybe HandleReferenceRule -> Maybe HandleReferenceRule -> Maybe (Maybe HandleReferenceRule)-mergeReferenceRule Nothing typePolicy = Just typePolicy-mergeReferenceRule profilePolicy Nothing = Just profilePolicy-mergeReferenceRule (Just profilePolicy) (Just typePolicy)- | profilePolicy ^. #localPrefix == typePolicy ^. #localPrefix,- Just mergedPattern <- mergeOptionalText (profilePolicy ^. #externalUriPattern) (typePolicy ^. #externalUriPattern) =- Just . Just $- HandleReferenceRule- { localPrefix = profilePolicy ^. #localPrefix,- externalUriSchemes =- filter- (`Set.member` Set.fromList (typePolicy ^. #externalUriSchemes))- (profilePolicy ^. #externalUriSchemes),- allowSelf = profilePolicy ^. #allowSelf && typePolicy ^. #allowSelf,- allowLocal = profilePolicy ^. #allowLocal && typePolicy ^. #allowLocal,- externalUriPattern = mergedPattern- }- | otherwise = Nothing--mergeUniqueBy :: Maybe Text -> Maybe Text -> Maybe (Maybe Text)-mergeUniqueBy = mergeOptionalText--mergeOptionalText :: Maybe Text -> Maybe Text -> Maybe (Maybe Text)-mergeOptionalText Nothing typeValue = Just typeValue-mergeOptionalText profileValue Nothing = Just profileValue-mergeOptionalText profileValue typeValue- | profileValue == typeValue = Just profileValue- | otherwise = Nothing--mergeFieldFormat :: Maybe FieldFormat -> Maybe FieldFormat -> Maybe (Maybe FieldFormat)-mergeFieldFormat Nothing typeFormat = Just typeFormat-mergeFieldFormat profileFormat Nothing = Just profileFormat-mergeFieldFormat (Just Uri) (Just typeFormat@(UriWithScheme _)) = Just (Just typeFormat)-mergeFieldFormat (Just profileFormat@(UriWithScheme _)) (Just Uri) = Just (Just profileFormat)-mergeFieldFormat (Just Actor) (Just HumanActor) = Just (Just HumanActor)-mergeFieldFormat (Just HumanActor) (Just Actor) = Just (Just HumanActor)-mergeFieldFormat (Just Integer) (Just NonNegativeInteger) = Just (Just NonNegativeInteger)-mergeFieldFormat (Just NonNegativeInteger) (Just Integer) = Just (Just NonNegativeInteger)-mergeFieldFormat profileFormat typeFormat- | profileFormat == typeFormat = Just profileFormat- | otherwise = Nothing--invalidFormatParameter :: FieldFormat -> Maybe (FieldFormat, Text)-invalidFormatParameter fieldFormat =- case fieldFormat of- UriWithScheme scheme- | not (validUriScheme scheme) -> Just (fieldFormat, scheme)- DocumentHandle prefix- | not (validDocumentHandlePrefix prefix) -> Just (fieldFormat, prefix)- _ -> Nothing--validUriScheme :: Text -> Bool-validUriScheme scheme =- case Text.uncons scheme of- Just (firstCharacter, rest) -> isAsciiLetter firstCharacter && Text.all validRest rest- Nothing -> False- where- validRest character =- isAsciiLetter character- || ('0' <= character && character <= '9')- || character `elem` ['+', '.', '-']--validDocumentHandlePrefix :: Text -> Bool-validDocumentHandlePrefix prefix =- case parseDocumentId (prefix <> "-1") of- Just documentId -> documentId ^. #prefix == prefix- Nothing -> False--isAsciiLetter :: Char -> Bool-isAsciiLetter character = isAsciiLower character || isAsciiUpper character--mergeCardinality :: Cardinality -> Cardinality -> Cardinality-mergeCardinality Any typeCardinality = typeCardinality-mergeCardinality profileCardinality Any = profileCardinality-mergeCardinality profileCardinality _typeCardinality = profileCardinality--mergeVocabulary :: [Text] -> [Text] -> [Text]-mergeVocabulary [] typeValues = typeValues-mergeVocabulary profileValues [] = profileValues-mergeVocabulary profileValues typeValues =- filter (`Set.member` Set.fromList typeValues) profileValues--deduplicate :: [Text] -> [Text]-deduplicate = go Set.empty- where- go _ [] = []- go seen (value : rest)- | value `Set.member` seen = go seen rest- | otherwise = value : go (Set.insert value seen) rest--deduplicateSchemes :: [Text] -> [Text]-deduplicateSchemes = go Set.empty- where- go _ [] = []- go seen (scheme : rest)- | normalized `Set.member` seen = go seen rest- | otherwise = scheme : go (Set.insert normalized seen) rest- where- normalized = Text.toCaseFold scheme--effectiveRulesForType :: CompiledProfile -> Text -> Map Text EffectiveFieldRule-effectiveRulesForType compiled ctype =- Map.findWithDefault (compiled ^. #baseRules) ctype (compiled ^. #rulesByType)--profileFieldDescriptionForType :: CompiledProfile -> Text -> Text -> Maybe Text-profileFieldDescriptionForType compiled ctype key =- Map.lookup key (effectiveRulesForType compiled ctype) >>= (^. #description)---- | A parsed document handle: an ASCII-letter-led alphanumeric prefix and a--- positive number, rendered as @PREFIX-N@.-data DocumentId = DocumentId- { prefix :: !Text,- number :: !Natural- }- deriving stock (Generic, Eq, Ord, Show)---- | Parse a strict document handle. The prefix contains one or more ASCII--- letters or digits and begins with a letter. It is followed by exactly one--- hyphen and a positive decimal number with no leading zero. Thus @ADR-7@--- parses, while @ADR-007@, @ADR-0@, @ADR-@, @-7@, @adr 7@, and--- @ADR-7-extra@ do not.-parseDocumentId :: Text -> Maybe DocumentId-parseDocumentId raw =- case Text.splitOn "-" raw of- [prefixText, numberText]- | validPrefix prefixText,- validNumberText numberText ->- case Text.Read.decimal numberText of- Right (parsedNumber, remainder)- | Text.null remainder,- parsedNumber > 0 ->- Just (DocumentId prefixText parsedNumber)- _ -> Nothing- _ -> Nothing- where- validPrefix value =- case Text.uncons value of- Just (firstCharacter, rest) ->- isAsciiLetter firstCharacter && Text.all isAsciiAlphaNumeric rest- Nothing -> False- validNumberText value =- case Text.uncons value of- Just (firstCharacter, rest) ->- firstCharacter >= '1'- && firstCharacter <= '9'- && Text.all isAsciiDigit rest- Nothing -> False- isAsciiDigit character =- character >= '0' && character <= '9'- isAsciiAlphaNumeric character =- isAsciiLetter character || isAsciiDigit character---- | Render a document handle as @PREFIX-N@.-renderDocumentId :: DocumentId -> Text-renderDocumentId DocumentId {prefix, number} =- prefix <> "-" <> Text.pack (show number)---- | Every well-formed handle under the profile's ID field, paired with the--- concept carrying it and sorted by prefix, number, then concept ID. Concepts--- without a well-formed handle are omitted. A profile with no ID field yields--- an empty list.-documentIdsInBundle :: ProfileSpec -> [Concept] -> [(DocumentId, ConceptId)]-documentIdsInBundle spec concepts =- case spec ^. #idField of- Nothing -> []- Just fieldName ->- List.sortOn- (\(documentId, cid) -> (documentId, renderConceptId cid))- [ (documentId, conceptIdOf concept)- | concept <- concepts,- Just (String rawDocumentId) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],- Just documentId <- [parseDocumentId rawDocumentId]- ]---- | Allocate one more than the highest document-ID number already used for the--- given prefix, or number 1 when the prefix is unused. Gaps are deliberately--- not filled: reusing a retired number could make an old reference silently--- point at a different document.-nextDocumentId :: ProfileSpec -> [Concept] -> Text -> DocumentId-nextDocumentId spec concepts requestedPrefix =- DocumentId- { prefix = requestedPrefix,- number = highestNumber + 1- }- where- highestNumber =- List.foldl'- max- 0- [ documentId ^. #number- | (documentId, _) <- documentIdsInBundle spec concepts,- documentId ^. #prefix == requestedPrefix- ]---- | One structural segment of a frontmatter path. EP-2 produces top-level--- 'FieldName' paths; later bounded nested validation can append field names and--- array indexes without encoding paths as ad hoc text.-data FieldPathSegment- = FieldName Text- | ArrayIndex Int- deriving stock (Generic, Eq, Ord, Show)--newtype FieldPath = FieldPath- { segments :: NonEmpty FieldPathSegment- }- deriving stock (Generic, Eq, Ord, Show)--topLevelFieldPath :: Text -> FieldPath-topLevelFieldPath key = FieldPath (FieldName key :| [])--nestedDefinitionPath :: Text -> Text -> FieldPath-nestedDefinitionPath parent child =- FieldPath (FieldName parent :| [FieldName child])--nestedValuePath :: Text -> Int -> Text -> FieldPath-nestedValuePath parent elementIndex child =- FieldPath (FieldName parent :| [ArrayIndex elementIndex, FieldName child])--nestedElementPath :: Text -> Int -> FieldPath-nestedElementPath parent elementIndex =- FieldPath (FieldName parent :| [ArrayIndex elementIndex])---- | A single deviation from a profile. Advisory by default at the CLI layer.-data ProfileViolation- = -- | concept's @type@ is not listed in the profile and unknown types are disallowed- TypeNotInProfile ConceptId Text- | -- | a required frontmatter key is missing or empty (concept, key, activating condition)- MissingProfileField ConceptId Text (Maybe FieldCondition)- | -- | a recommended frontmatter key is missing under strict authoring- MissingRecommendedProfileField ConceptId Text (Maybe FieldCondition)- | -- | a required field is missing inside one list element record- MissingNestedProfileField ConceptId FieldPath (Maybe FieldCondition)- | -- | a recommended nested field is missing under strict authoring- MissingRecommendedNestedProfileField ConceptId FieldPath (Maybe FieldCondition)- | -- | a present value is outside the effective textual vocabulary- ValueNotInVocabulary ConceptId FieldPath [Text] Value- | -- | a present value has the wrong scalar/list shape- CardinalityMismatch ConceptId FieldPath Cardinality Value- | -- | a present value does not satisfy its named textual format- ValueFormatMismatch ConceptId FieldPath FieldFormat Value- | -- | a local reference has the right prefix but no valid owner in this bundle- DanglingHandleReference ConceptId FieldPath Text- | -- | a parsed local handle uses a prefix other than the declared target prefix- ReferenceHandlePrefixMismatch ConceptId FieldPath Text Text- | -- | a reference value is neither textual local handle nor valid absolute URI- MalformedDocumentReference ConceptId FieldPath Value- | -- | an absolute external URI uses a scheme the profile did not permit- ExternalReferenceSchemeNotAllowed ConceptId FieldPath Text [Text]- | -- | a syntactically valid local handle is prohibited at this field- LocalDocumentReferenceNotAllowed ConceptId FieldPath Text- | -- | an allowed external URI does not match the declared whole-value pattern- ExternalReferencePatternMismatch ConceptId FieldPath Text Text- | -- | a local handle, or a bundle path, resolves to the concept carrying it- SelfDocumentReference ConceptId FieldPath Text- | -- | a path-valued field's value is not one of the three shapes of §6.2- MalformedPathReference ConceptId FieldPath Value- | -- | a relative path climbs above the bundle root- PathEscapesBundle ConceptId FieldPath Text- | -- | a bundle path names a concept that does not exist in this bundle- DanglingPathReference ConceptId FieldPath Text- | -- | a closed profile does not declare this top-level field- FieldNotInProfile ConceptId Text- | -- | a declared list element is not an object record- NestedElementNotRecord ConceptId FieldPath Value- | -- | one valid scalar member value occurs in more than one list element- DuplicateNestedFieldValue ConceptId FieldPath Value (NonEmpty Int)- | -- | concept's file path does not match the type rule's pattern (concept, type, pattern)- PathPatternMismatch ConceptId Text Text- | -- | type rule requires a resource scheme but resource is absent (concept, type, scheme)- MissingResource ConceptId Text Text- | -- | resource present but its scheme is wrong (concept, expected scheme, actual resource)- ResourceSchemeMismatch ConceptId Text Text- | -- | required @# Schema@ section is absent (concept, type)- MissingSchemaSection ConceptId Text- | -- | @# Schema@ table columns do not match (concept, type, expected, actual)- SchemaColumnsMismatch ConceptId Text [Text] [Text]- | -- | type rule declares an @idPrefix@ but the concept has no handle (concept, type, prefix)- MissingDocumentId ConceptId Text Text- | -- | handle present but malformed for the declared prefix (concept, prefix, actual value)- MalformedDocumentId ConceptId Text Text- | -- | the same handle appears on more than one concept (handle, concept, other concept)- DuplicateDocumentId Text ConceptId ConceptId- | -- | the profile requires the bundle to declare an OKF version it does not- -- (required version, what the bundle declared, 'Nothing' when undeclared)- --- -- The only violation that belongs to the bundle rather than to a concept, so- -- a consumer grouping violations by 'ConceptId' has nothing to key it on.- RequiredBundleVersionUnmet Text (Maybe Text)- deriving stock (Generic, Eq, Show)---- | Check every concept against a compiled profile, returning all deviations.--- Profile-wide rules apply even to unknown types; a matching type rule adds its--- frontmatter rules and the existing type-specific structural checks.------ Existence is decided against the concepts themselves, so a @path@ rule can--- tell whether a target names a concept and cannot tell whether it names--- @references\/attesters\/revenue.py@; such a target is accepted unchecked.--- Callers holding a real directory should use 'validateProfileWith'.-validateProfile :: ValidationProfile -> CompiledProfile -> [Concept] -> [ProfileViolation]-validateProfile =- validateProfileWithPresence conceptsOnly bundleInventoryOfConcepts- where- -- Narrowed to @.md@ on purpose. A caller with concepts and no directory can- -- decide a @.md@ target, because a 'Concept' /is/ a non-reserved @.md@- -- file — but it has never been able to see §6.3's- -- @references/attesters/revenue.py@, and answering 'TargetAbsent' for it- -- would turn a question okf cannot ask into a rejection.- conceptsOnly conceptInventory resolved- | FilePath.takeExtension resolved /= ".md" = TargetUnknown- | bundleInventoryMember resolved conceptInventory = TargetPresent- | otherwise = TargetAbsent---- | 'validateProfile' with the bundle's full file inventory, so a @path@ rule--- can decide whether a target that is not a concept exists.------ Specification §6.3's own example of a path target is--- @references\/attesters\/revenue.py@, which is not a concept and which--- 'validateProfile' therefore accepts unchecked. A caller that walked a real--- directory has 'Okf.Bundle.walkBundleInventory' and can do better. See--- @docs\/adr\/13-the-references-convention-and-non-markdown-files.md@.------ Passing an inventory does not make validation touch the filesystem. The--- inventory is data read once during the bundle walk, where okf is already doing--- IO, which is the shape @docs\/adr\/5-compile-profile-rules-before-validation.md@--- requires and the one the core §6.2 check already uses.-validateProfileWith :: BundleInventory -> ValidationProfile -> CompiledProfile -> [Concept] -> [ProfileViolation]-validateProfileWith inventory =- validateProfileWithPresence everyFile (const inventory)- where- everyFile fullInventory resolved- | bundleInventoryMember resolved fullInventory = TargetPresent- | otherwise = TargetAbsent---- | Check a bundle's specification §12 version declaration against the profile's--- @requireBundleVersion@ setting. Returns @[]@ when the profile requires nothing,--- which is the default and the case for almost every profile.------ Deliberately a separate entry point rather than a parameter on--- 'validateProfile' and 'validateProfileWith'. Those two are public and their--- signatures are depended on downstream, and this check consults no concepts at--- all: threading a bundle-scoped question through a concept-walking function--- would be misleading as well as breaking.------ §12 makes the declaration a MAY, so okf's own validator never asks for one —--- see @docs\/adr\/10-okf-version-declaration-and-best-effort-reading.md@. This is--- the house-convention half: nothing is reported unless a profile author wrote--- the field.------ A declaration okf cannot parse counts as unmet. It cannot be compared, okf--- already reports it separately as a strict-mode authoring lint, and silently--- passing it would let a typo satisfy a requirement.-validateProfileVersion :: VersionDeclaration -> CompiledProfile -> [ProfileViolation]-validateProfileVersion declaration compiled =- case compiledProfileRequiredBundleVersion compiled of- Nothing -> []- Just required ->- let unmet = [RequiredBundleVersionUnmet (renderOkfVersion required) declaredText]- in case declaration of- -- A bundle ahead of the house minimum is not a deviation: §12 defines- -- a minor bump as backward-compatible additions.- VersionDeclared declared | declared >= required -> []- VersionDeclared _ -> unmet- VersionUnparseable _ -> unmet- VersionUndeclared -> unmet- where- declaredText = case declaration of- VersionDeclared declared -> Just (renderOkfVersion declared)- VersionUnparseable rawVersion -> Just rawVersion- VersionUndeclared -> Nothing---- | The shared body of 'validateProfile' and 'validateProfileWith'.------ The first argument decides what a @path@ rule may conclude about a resolved--- §6.2 target; the second supplies the inventory it consults, derived from the--- concepts when the caller has no directory to walk. Keeping one implementation--- is what stops the two entry points from drifting apart on everything /except/--- the one question that distinguishes them.-validateProfileWithPresence ::- (BundleInventory -> FilePath -> PathTargetPresence) ->- ([Concept] -> BundleInventory) ->- ValidationProfile ->- CompiledProfile ->- [Concept] ->- [ProfileViolation]-validateProfileWithPresence presenceRule inventoryOf validationProfile compiled concepts =- concatMap checkConcept sortedConcepts <> checkDuplicateDocumentIds spec sortedConcepts- where- spec = compiledProfileSpec compiled- sortedConcepts = List.sortOn (renderConceptId . conceptIdOf) concepts- rulesByType = [(rule ^. #type_, rule) | rule <- spec ^. #types]- validDocumentIdIndex = buildValidDocumentIdIndex spec rulesByType sortedConcepts- -- What this caller can say about a resolved §6.2 path, built once.- presenceOf = presenceRule (inventoryOf sortedConcepts)-- checkConcept concept =- let cid = conceptIdOf concept- ctype = conceptType concept- fieldViolations = checkFields cid ctype concept <> checkUnknownFields cid ctype concept- in case lookup ctype rulesByType of- Nothing ->- [TypeNotInProfile cid ctype | not (spec ^. #allowUnknownTypes)] <> fieldViolations- Just rule ->- fieldViolations- <> checkPath cid ctype rule- <> checkResource cid ctype rule concept- <> checkSchema cid ctype rule concept- <> checkDocumentId spec cid ctype rule concept-- checkFields cid ctype concept =- concatMap checkField (Map.toAscList (effectiveRulesForType compiled ctype))- where- checkField (key, rule) =- case evaluateFieldValue rule (frontmatterLookup key (conceptFrontmatter concept)) of- FieldAbsent actual ->- presenceViolations key rule- <> maybe [] (vocabularyViolations key rule) actual- <> maybe [] (formatViolations key rule) actual- <> maybe [] (referenceViolations (topLevelFieldPath key) rule) actual- <> maybe [] (pathViolations (topLevelFieldPath key) rule) actual- FieldPresent actual ->- vocabularyViolations key rule actual- <> formatViolations key rule actual- <> referenceViolations (topLevelFieldPath key) rule actual- <> pathViolations (topLevelFieldPath key) rule actual- <> nestedViolations key rule actual- <> objectViolations key rule actual- FieldWrongShape actual -> [CardinalityMismatch cid (topLevelFieldPath key) (rule ^. #cardinality) actual]-- presenceViolations key rule =- case applicablePresenceClause validationProfile (\sourceKey -> frontmatterLookup sourceKey (conceptFrontmatter concept)) rule of- Nothing -> []- Just clause ->- [ case clause ^. #requirement of- RequiredField -> MissingProfileField cid key (clause ^. #condition)- RecommendedField -> MissingRecommendedProfileField cid key (clause ^. #condition)- ]- vocabularyViolations key rule actual =- [ ValueNotInVocabulary cid (topLevelFieldPath key) (rule ^. #allowedValues) actual- | not (null (rule ^. #allowedValues)),- not (valueMatchesVocabulary (rule ^. #allowedValues) actual)- ]- formatViolations key rule actual =- [ ValueFormatMismatch cid (topLevelFieldPath key) fieldFormat actual- | Just fieldFormat <- [rule ^. #format],- not (valueMatchesFormat fieldFormat actual)- ]- referenceViolations fieldPath rule actual =- case rule ^. #reference of- Nothing -> []- Just policy -> validateReferenceValue validDocumentIdIndex cid fieldPath policy (rule ^. #compiledExternalUriPattern) actual-- -- Takes a 'FieldPath' rather than a key because it is shared by all- -- three scopes: a top-level key, a member of a list element, and a- -- member of an object-valued mapping.- pathViolations fieldPath rule actual =- case rule ^. #path of- Nothing -> []- Just policy -> validatePathValue presenceOf cid fieldPath policy actual-- nestedViolations parentKey parentRule = \case- Array elementValues- | Just nestedRules <- parentRule ^. #elementFields ->- let indexedElements = zip [0 ..] (Vector.toList elementValues)- in concat- [ case elementValue of- Aeson.Object members ->- concatMap- (checkRecordMember (nestedValuePath parentKey elementIndex) members)- (Map.toAscList nestedRules)- _ -> [NestedElementNotRecord cid (nestedElementPath parentKey elementIndex) elementValue]- | (elementIndex, elementValue) <- indexedElements- ]- <> uniquenessViolations parentKey parentRule nestedRules indexedElements- _ -> []-- -- The mapping spelling of the same idea. The value /is/ the record, so- -- there is no index in the path: a member is reported as- -- @generated.by@ rather than @generated[0].by@.- objectViolations parentKey parentRule = \case- Aeson.Object members- | Just objectRules <- parentRule ^. #objectFields ->- concatMap- (checkRecordMember (nestedDefinitionPath parentKey) members)- (Map.toAscList objectRules)- _ -> []-- -- Check one member of one record. Shared by the list-element and- -- object-value walks, which differ only in how they name the member;- -- the sibling lookup that resolves a @when@ condition stays scoped to- -- the record the member lives in either way.- checkRecordMember buildPath members (key, rule) =- let path = buildPath key- actualValue = Aeson.KeyMap.lookup (Aeson.Key.fromText key) members- in case evaluateFieldValue rule actualValue of- FieldAbsent actual ->- nestedPresenceViolations members path rule- <> maybe [] (nestedVocabularyViolations path rule) actual- <> maybe [] (nestedFormatViolations path rule) actual- <> maybe [] (referenceViolations path rule) actual- <> maybe [] (pathViolations path rule) actual- FieldPresent actual ->- nestedVocabularyViolations path rule actual- <> nestedFormatViolations path rule actual- <> referenceViolations path rule actual- <> pathViolations path rule actual- FieldWrongShape actual -> [CardinalityMismatch cid path (rule ^. #cardinality) actual]-- nestedPresenceViolations objectFields path rule =- case applicablePresenceClause validationProfile lookupSibling rule of- Nothing -> []- Just clause ->- [ case clause ^. #requirement of- RequiredField -> MissingNestedProfileField cid path (clause ^. #condition)- RecommendedField -> MissingRecommendedNestedProfileField cid path (clause ^. #condition)- ]- where- lookupSibling sourceKey = Aeson.KeyMap.lookup (Aeson.Key.fromText sourceKey) objectFields- nestedVocabularyViolations path rule actual =- [ ValueNotInVocabulary cid path (rule ^. #allowedValues) actual- | not (null (rule ^. #allowedValues)),- not (valueMatchesVocabulary (rule ^. #allowedValues) actual)- ]- nestedFormatViolations path rule actual =- [ ValueFormatMismatch cid path fieldFormat actual- | Just fieldFormat <- [rule ^. #format],- not (valueMatchesFormat fieldFormat actual)- ]-- uniquenessViolations parentKey parentRule nestedRules indexedElements =- case parentRule ^. #uniqueBy of- Nothing -> []- Just key ->- case Map.lookup key nestedRules of- Nothing -> []- Just keyRule ->- [ DuplicateNestedFieldValue cid (nestedDefinitionPath parentKey key) duplicateValue (firstIndex :| remainingIndices)- | (duplicateValue, firstIndex : remainingIndices) <- groupedValues key keyRule indexedElements,- not (null remainingIndices)- ]-- groupedValues key keyRule =- List.foldl' insertValue [] . mapMaybe participant- where- participant (elementIndex, Aeson.Object members) =- case evaluateFieldValue keyRule (Aeson.KeyMap.lookup (Aeson.Key.fromText key) members) of- FieldPresent scalarValue -> Just (scalarValue, elementIndex)- _ -> Nothing- participant _ = Nothing-- insertValue [] (value, elementIndex) = [(value, [elementIndex])]- insertValue ((groupValue, elementIndices) : remaining) (value, elementIndex)- | groupValue == value = (groupValue, elementIndices <> [elementIndex]) : remaining- | otherwise = (groupValue, elementIndices) : insertValue remaining (value, elementIndex)-- checkUnknownFields cid ctype concept- | spec ^. #allowUnknownFields = []- | otherwise =- [ FieldNotInProfile cid key- | key <- frontmatterKeys (conceptFrontmatter concept),- key `Set.notMember` allowedFields ctype- ]-- allowedFields ctype =- coreFrontmatterFields- <> Map.keysSet (effectiveRulesForType compiled ctype)- <> maybe Set.empty Set.singleton (spec ^. #idField)---- | Index only handles that are valid owners under the compiled profile's raw--- type declarations: the concept has a matching type rule, that rule declares--- the parsed handle's exact prefix, and the profile declares the owning field.--- Multiple owners are retained so a duplicate target still counts as existing;--- the existing bundle-wide duplicate diagnostic remains authoritative.-buildValidDocumentIdIndex :: ProfileSpec -> [(Text, TypeRule)] -> [Concept] -> Map DocumentId [ConceptId]-buildValidDocumentIdIndex spec rulesByType concepts =- case spec ^. #idField of- Nothing -> Map.empty- Just fieldName ->- Map.fromListWith- (flip (<>))- [ (documentId, [conceptIdOf concept])- | concept <- concepts,- Just typeRule <- [lookup (conceptType concept) rulesByType],- Just expectedPrefix <- [typeRule ^. #idPrefix],- Just (String rawDocumentId) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],- Just documentId <- [parseDocumentId rawDocumentId],- documentId ^. #prefix == expectedPrefix- ]--validateReferenceValue :: Map DocumentId [ConceptId] -> ConceptId -> FieldPath -> HandleReferenceRule -> Maybe Regex -> Value -> [ProfileViolation]-validateReferenceValue validOwners sourceConcept path policy matcher = \case- String rawReference -> validateReferenceText validOwners sourceConcept path policy matcher rawReference- Array values ->- concat- [ case value of- String rawReference -> validateReferenceText validOwners sourceConcept (appendArrayIndex path elementIndex) policy matcher rawReference- _ -> [MalformedDocumentReference sourceConcept (appendArrayIndex path elementIndex) value]- | (elementIndex, value) <- zip [0 ..] (Vector.toList values)- ]- actual -> [MalformedDocumentReference sourceConcept path actual]--validateReferenceText :: Map DocumentId [ConceptId] -> ConceptId -> FieldPath -> HandleReferenceRule -> Maybe Regex -> Text -> [ProfileViolation]-validateReferenceText validOwners sourceConcept path policy matcher rawReference =- case parseDocumentId rawReference of- Just documentId- | not (policy ^. #allowLocal) ->- [LocalDocumentReferenceNotAllowed sourceConcept path rawReference]- | documentId ^. #prefix /= policy ^. #localPrefix ->- [ReferenceHandlePrefixMismatch sourceConcept path rawReference (policy ^. #localPrefix)]- | otherwise ->- case Map.lookup documentId validOwners of- Nothing -> [DanglingHandleReference sourceConcept path rawReference]- Just owners- | not (policy ^. #allowSelf),- sourceConcept `elem` owners ->- [SelfDocumentReference sourceConcept path rawReference]- | otherwise -> []- Nothing ->- case parseURI (Text.unpack rawReference) of- Just parsed- | not (Text.null normalizedScheme),- normalizedScheme `elem` policy ^. #externalUriSchemes ->- case (policy ^. #externalUriPattern, matcher) of- (Nothing, _) -> []- (Just patternText, Just compiledPattern)- | matchesWholeExternalUriPattern compiledPattern rawReference -> []- | otherwise -> [ExternalReferencePatternMismatch sourceConcept path rawReference patternText]- (Just patternText, Nothing) -> [ExternalReferencePatternMismatch sourceConcept path rawReference patternText]- | not (Text.null normalizedScheme) ->- [ ExternalReferenceSchemeNotAllowed- sourceConcept- path- normalizedScheme- (policy ^. #externalUriSchemes)- ]- | otherwise -> [MalformedDocumentReference sourceConcept path (String rawReference)]- where- normalizedScheme = Text.toCaseFold (Text.dropWhileEnd (== ':') (Text.pack (uriScheme parsed)))- Nothing -> [MalformedDocumentReference sourceConcept path (String rawReference)]---- | Check one path-valued frontmatter value, at any scope. A list is checked--- element-wise so that a diagnostic names @sources[1].resource@ rather than the--- whole list.-validatePathValue :: (FilePath -> PathTargetPresence) -> ConceptId -> FieldPath -> PathReferenceRule -> Value -> [ProfileViolation]-validatePathValue presenceOf sourceConcept path policy = \case- String rawPath -> validatePathText presenceOf sourceConcept path policy rawPath- Array values ->- concat- [ case value of- String rawPath ->- validatePathText presenceOf sourceConcept (appendArrayIndex path elementIndex) policy rawPath- _ -> [MalformedPathReference sourceConcept (appendArrayIndex path elementIndex) value]- | (elementIndex, value) <- zip [0 ..] (Vector.toList values)- ]- actual -> [MalformedPathReference sourceConcept path actual]---- | What a caller can say about a resolved §6.2 bundle path.------ Three answers rather than two, because "I cannot tell" is a real state and--- collapsing it into "absent" turns silence into a rejection. 'validateProfile'--- is handed concepts alone, so it genuinely cannot say whether §6.3's--- @references\/attesters\/revenue.py@ is there, and reporting it as dangling--- would be a claim okf did not check.-data PathTargetPresence- = -- | The bundle holds it.- TargetPresent- | -- | The bundle does not hold it, and the caller can see enough to be sure.- TargetAbsent- | -- | The caller cannot answer for this path.- TargetUnknown- deriving stock (Generic, Eq, Show)---- | Check one path value against the §6.2 grammar and the profile's policy.------ Every diagnostic carries the raw text the author wrote rather than the--- collapsed path okf computed from it, so the message names something findable--- in the file.------ Existence is decided by the supplied lookup, which reports what the caller can--- actually see: 'validateProfileWith' holds a real directory's inventory and so--- resolves §6.3's own @references\/attesters\/revenue.py@, while--- 'validateProfile' holds the concepts alone and answers 'TargetUnknown' for it.------ The @.md@ branch still goes through 'conceptIdFromFilePath', because that is--- what decides self-reference and what makes 'MalformedPathReference' reachable--- for a path no concept ID could be built from. What it no longer decides is--- whether the target is there.-validatePathText :: (FilePath -> PathTargetPresence) -> ConceptId -> FieldPath -> PathReferenceRule -> Text -> [ProfileViolation]-validatePathText presenceOf sourceConcept path policy rawPath =- case classifyPathReference sourceConcept rawPath of- ExternalUrl scheme- | scheme `elem` policy ^. #externalUriSchemes -> []- | otherwise ->- [ExternalReferenceSchemeNotAllowed sourceConcept path scheme (policy ^. #externalUriSchemes)]- EscapesBundle -> [PathEscapesBundle sourceConcept path rawPath]- MalformedPath -> [MalformedPathReference sourceConcept path (String rawPath)]- BundlePath resolved- | FilePath.takeExtension resolved == ".md" ->- case conceptIdFromFilePath resolved of- Left _ -> [MalformedPathReference sourceConcept path (String rawPath)]- Right target- | target == sourceConcept,- not (policy ^. #allowSelf) ->- [SelfDocumentReference sourceConcept path rawPath]- | otherwise -> danglingWhenAbsent resolved- | otherwise -> danglingWhenAbsent resolved- where- danglingWhenAbsent resolved = case presenceOf resolved of- TargetAbsent -> [DanglingPathReference sourceConcept path rawPath]- TargetPresent -> []- TargetUnknown -> []--appendArrayIndex :: FieldPath -> Int -> FieldPath-appendArrayIndex (FieldPath pathSegments) elementIndex =- FieldPath (pathSegments <> (ArrayIndex elementIndex :| []))--applicablePresenceClause :: ValidationProfile -> (Text -> Maybe Value) -> EffectiveFieldRule -> Maybe PresenceClause-applicablePresenceClause validationProfile lookupValue rule =- List.find applies requiredClauses- <|> if validationProfile == StrictAuthoring then List.find applies recommendedClauses else Nothing- where- requiredClauses = filter ((== RequiredField) . (^. #requirement)) (rule ^. #presenceClauses)- recommendedClauses = filter ((== RecommendedField) . (^. #requirement)) (rule ^. #presenceClauses)- applies clause = maybe True conditionApplies (clause ^. #condition)- conditionApplies condition =- case lookupValue (condition ^. #field) of- Just (String actual) -> actual `elem` condition ^. #hasValue- _ -> False--valueMatchesVocabulary :: [Text] -> Value -> Bool-valueMatchesVocabulary allowed = \case- String value -> value `elem` allowed- Array values -> all elementMatches (Vector.toList values)- _ -> False- where- elementMatches (String value) = value `elem` allowed- elementMatches _ = False---- | Whether a present value satisfies a format. Each format declares which--- value shapes it can match at all: the textual formats match a string, the--- numeric ones a YAML number, and 'Boolean' a YAML boolean. A list is matched--- by recursing, so a list of integers satisfies an @integer@ format for the--- same reason a list of timestamps satisfies @rfc3339-utc@ — a format--- constrains a value, and a list is a list of values.-valueMatchesFormat :: FieldFormat -> Value -> Bool-valueMatchesFormat fieldFormat actual =- case actual of- String value -> textMatchesFormat fieldFormat value- Array values -> all (valueMatchesFormat fieldFormat) (Vector.toList values)- Number _ -> numberMatchesFormat fieldFormat actual- Bool _ -> fieldFormat == Boolean- _ -> False--textMatchesFormat :: FieldFormat -> Text -> Bool-textMatchesFormat fieldFormat value =- case fieldFormat of- Rfc3339Utc ->- hasExtendedUtcShape value- && isJust (iso8601ParseM (Text.unpack value) :: Maybe UTCTime)- Date ->- hasExtendedDateShape value- && isJust (iso8601ParseM (Text.unpack value) :: Maybe Day)- Uri -> isJust (parseURI (Text.unpack value))- UriWithScheme expectedScheme ->- case parseURI (Text.unpack value) of- Just parsed ->- Text.toCaseFold (Text.dropWhileEnd (== ':') (Text.pack (uriScheme parsed)))- == Text.toCaseFold expectedScheme- Nothing -> False- DocumentHandle expectedPrefix ->- case parseDocumentId value of- Just documentId -> documentId ^. #prefix == expectedPrefix- Nothing -> False- -- Specification §7 defines exactly three actor shapes, and- -- 'Actor.parseActor' classifies them, so the format is a case match on its- -- result rather than a second parser. A value the convention does not- -- define — the specification's own illustrative @team:ga4-docs@ among them —- -- is 'Actor.UnclassifiedActor' and is reported.- Actor ->- case Actor.parseActor value of- Actor.UnclassifiedActor _ -> False- _ -> True- HumanActor -> Actor.isHumanActor (Actor.parseActor value)- -- A numeric or boolean format never matches text. A @usage_count: "5000"@- -- is a quoted string in YAML and is reported rather than coerced, which is- -- the whole point of being able to declare the format.- Integer -> False- NonNegativeInteger -> False- Boolean -> False---- | Whether a YAML number satisfies a numeric format. Uses aeson's own--- 'Integer' decoder, which already rejects a non-integral number, rather than--- reaching for @scientific@: that package is a dependency of @aeson@ but not of--- @okf-core@, so it is importable in a scratch experiment and not from here.--- 'Okf.Document.objectInteger' takes the same route.-numberMatchesFormat :: FieldFormat -> Value -> Bool-numberMatchesFormat fieldFormat actual =- case Aeson.fromJSON actual :: Aeson.Result Integer of- Aeson.Error _ -> False- Aeson.Success parsed ->- case fieldFormat of- Integer -> True- NonNegativeInteger -> parsed >= 0- _ -> False--hasExtendedDateShape :: Text -> Bool-hasExtendedDateShape value =- Text.length value == 10- && Text.index value 4 == '-'- && Text.index value 7 == '-'--hasExtendedUtcShape :: Text -> Bool-hasExtendedUtcShape value =- Text.length value >= 20- && Text.index value 4 == '-'- && Text.index value 7 == '-'- && Text.index value 10 == 'T'- && Text.index value 13 == ':'- && Text.index value 16 == ':'- && Text.last value == 'Z'--data FieldValueEvaluation- = FieldAbsent (Maybe Value)- | FieldPresent Value- | FieldWrongShape Value---- | Decide whether a frontmatter value counts as present for one rule. Takes the--- whole rule rather than just its cardinality because the 'Any' case has to know--- whether the rule declares object members: a rule accepting both spellings of--- the OKF v0.2 @verified@ key stays at 'Any' cardinality and must still count a--- mapping as present.------ This is the single point at which okf decides presence, so a change here--- affects every profile check.-evaluateFieldValue :: EffectiveFieldRule -> Maybe Value -> FieldValueEvaluation-evaluateFieldValue _ Nothing = FieldAbsent Nothing-evaluateFieldValue rule (Just actual) =- case rule ^. #cardinality of- Any- | legacyValueIsPresent actual -> FieldPresent actual- -- A rule that also accepts a mapping counts a non-empty one as present.- -- Without this a profile declaring both shapes would report `verified`- -- missing on a document that writes it as a bare mapping.- | isJust (rule ^. #objectFields), nonEmptyObject actual -> FieldPresent actual- | otherwise -> FieldAbsent (Just actual)- Scalar ->- case actual of- String value- | Text.null (Text.strip value) -> FieldAbsent (Just actual)- | otherwise -> FieldPresent actual- Number _ -> FieldPresent actual- Bool _ -> FieldPresent actual- _ -> FieldWrongShape actual- List ->- case actual of- Array values- | null values -> FieldAbsent (Just actual)- | otherwise -> FieldPresent actual- _ -> FieldWrongShape actual- Object- -- An empty mapping is treated as absent for the same reason an empty list- -- is: a key written with no content is the author saying nothing, and- -- reporting it missing is more useful than reporting it present and empty.- | nonEmptyObject actual -> FieldPresent actual- | isObject actual -> FieldAbsent (Just actual)- | otherwise -> FieldWrongShape actual--isObject :: Value -> Bool-isObject = \case- Aeson.Object _ -> True- _ -> False--nonEmptyObject :: Value -> Bool-nonEmptyObject = \case- Aeson.Object members -> not (Aeson.KeyMap.null members)- _ -> False--legacyValueIsPresent :: Value -> Bool-legacyValueIsPresent = \case- String value -> not (Text.null (Text.strip value))- Array values -> not (null values)- _ -> False---- | Check a profile-declared document ID for one concept.-checkDocumentId :: ProfileSpec -> ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]-checkDocumentId spec cid ctype rule concept =- case (spec ^. #idField, rule ^. #idPrefix) of- (Just fieldName, Just expectedPrefix) ->- case frontmatterLookup fieldName (conceptFrontmatter concept) of- Just (String value)- | not (Text.null (Text.strip value)) ->- case parseDocumentId value of- Just documentId- | documentId ^. #prefix == expectedPrefix -> []- _ -> [MalformedDocumentId cid expectedPrefix value]- _ -> [MissingDocumentId cid ctype expectedPrefix]- _ -> []---- | Check every non-empty value under the profile's ID field for bundle-wide--- uniqueness. Concept IDs are sorted before grouping so output is deterministic.-checkDuplicateDocumentIds :: ProfileSpec -> [Concept] -> [ProfileViolation]-checkDuplicateDocumentIds spec concepts =- case spec ^. #idField of- Nothing -> []- Just fieldName ->- concatMap duplicateViolations (groupedHandles fieldName)- where- groupedHandles fieldName =- List.groupBy- (\(leftHandle, _) (rightHandle, _) -> leftHandle == rightHandle)- (handles fieldName)- handles fieldName =- List.sortOn- (\(handle, cid) -> (handle, renderConceptId cid))- [ (handle, conceptIdOf concept)- | concept <- concepts,- Just (String handle) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],- not (Text.null (Text.strip handle))- ]- duplicateViolations ((handle, firstConcept) : duplicates) =- [ DuplicateDocumentId handle firstConcept duplicateConcept- | (_, duplicateConcept) <- duplicates- ]- duplicateViolations [] = []---- | Project a concept's frontmatter (the document's @frontmatter@ field).-conceptFrontmatter :: Concept -> Frontmatter-conceptFrontmatter concept = conceptDocument concept ^. #frontmatter---- | A type rule's @pathPattern@, when present, constrains where the concept's--- file may live.-checkPath :: ConceptId -> Text -> TypeRule -> [ProfileViolation]-checkPath cid ctype rule =- case rule ^. #pathPattern of- Nothing -> []- Just patternText- | matchPathPattern patternText cid -> []- | otherwise -> [PathPatternMismatch cid ctype patternText]---- | Match a concept ID against a segment-glob pattern. @*@ matches exactly one--- segment; a single trailing @**@ matches one or more remaining segments; every--- other segment matches literally. Both segment lists must be consumed exactly,--- except for the trailing @**@ case.-matchPathPattern :: Text -> ConceptId -> Bool-matchPathPattern patternText cid =- go (Text.splitOn "/" patternText) (Text.splitOn "/" (renderConceptId cid))- where- go [] [] = True- go ["**"] (_ : _) = True- go ("*" : ps) (_ : ss) = go ps ss- go (p : ps) (s : ss) = p == s && go ps ss- go _ _ = False---- | A type rule's @resourceScheme@, when present, requires a @resource:@ value--- whose scheme matches.-checkResource :: ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]-checkResource cid ctype rule concept =- case rule ^. #resourceScheme of- Nothing -> []- Just scheme ->- case conceptResource concept of- Nothing -> [MissingResource cid ctype scheme]- Just value- | (scheme <> "://") `Text.isPrefixOf` value -> []- | otherwise -> [ResourceSchemeMismatch cid scheme value]---- | A type rule's @# Schema@ contract: when @requireSchemaSection@ is set, the--- body must contain a @# Schema@ section whose table header begins with the--- required @schemaColumns@ (case-insensitive, trimmed, compared as a prefix so a--- team may add trailing columns without tripping the check).-checkSchema :: ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]-checkSchema cid ctype rule concept- | not (rule ^. #requireSchemaSection) = []- | otherwise =- case schemaSectionColumns (conceptDocument concept ^. #body) of- Nothing -> [MissingSchemaSection cid ctype]- Just actual ->- let expected = rule ^. #schemaColumns- norm = map (Text.toLower . Text.strip)- in [ SchemaColumnsMismatch cid ctype expected actual- | not (norm expected `List.isPrefixOf` norm actual)- ]---- | The header-row columns of the first GitHub-flavored table that follows the--- first top-level @# Schema@ heading, or 'Nothing' if there is no Schema heading--- or no following table. Columns are trimmed.-schemaSectionColumns :: Text -> Maybe [Text]-schemaSectionColumns markdown =- let CMarkGFM.Node _ _ topLevel = CMarkGFM.commonmarkToNode markdownOptions [CMarkGFM.extTable] markdown- in firstTableAfterSchema topLevel--firstTableAfterSchema :: [CMarkGFM.Node] -> Maybe [Text]-firstTableAfterSchema topLevel =- case dropWhile (not . isSchemaHeading) topLevel of- (_heading : rest) -> headerRow rest- [] -> Nothing- where- isSchemaHeading (CMarkGFM.Node _ (CMarkGFM.HEADING _) inner) =- Text.toLower (Text.strip (nodeText inner)) == "schema"- isSchemaHeading _ = False-- headerRow [] = Nothing- headerRow (CMarkGFM.Node _ (CMarkGFM.TABLE _) tableChildren : _) =- case tableChildren of- (CMarkGFM.Node _ CMarkGFM.TABLE_ROW cells : _) -> Just (map cellText cells)- _ -> Nothing- headerRow (_ : more) = headerRow more-- cellText (CMarkGFM.Node _ _ inner) = Text.strip (nodeText inner)---- | Concatenate all @TEXT@/@CODE@ literals under a node list, recursively.-nodeText :: [CMarkGFM.Node] -> Text-nodeText = foldMap go- where- go (CMarkGFM.Node _ (CMarkGFM.TEXT t) _) = t- go (CMarkGFM.Node _ (CMarkGFM.CODE t) _) = t- go (CMarkGFM.Node _ _ inner) = nodeText inner+ ValueSelector (..),+ PresenceConstraint (..),+ TimestampOrderConstraint (..),+ MembershipConstraint (..),+ UniqueValuesConstraint (..),+ EffectiveConceptConstraints,+ conceptPresenceConstraints,+ conceptResolvedSelectors,+ conceptTimestampOrderConstraints,+ conceptMembershipConstraints,+ conceptUniqueValuesConstraints,+ compiledProfileBaseConstraints,+ compiledProfileConstraintsForType,+ fieldRuleElementConstraints,+ fieldRuleObjectConstraints,+ resolveValueSelector,+ renderConceptConstraints,+ renderValueSelector,+ frontmatterConstraints,+ nestedConstraints,+ FrontmatterRules (..),+ FieldCondition (..),+ HandleReferenceRule (..),+ PathReferenceRule (..),+ FieldRule (..),+ NestedRules (..),+ NestedFieldRule (..),+ Cardinality (..),+ FieldFormat (..),+ TypeRule (..),+ FieldPath (..),+ FieldPathSegment (..),+ loadProfileFile,+ decodeProfileExpr,+ profileFieldDescription,+ CompiledProfile,+ ProfileDefinitionError (..),+ compileProfile,+ compiledProfileSpec,+ compiledProfileRequiredBundleVersion,+ profileFieldDescriptionForType,++ -- * Compiled rule inspection++ -- | A read-only window onto what a compiled profile actually demands. The+ -- rule types are abstract on purpose: read them through the accessors below+ -- so that later profile features can extend the compiled encoding without+ -- breaking consumers.+ --+ -- Two encodings are easy to misread and are worth stating up front. An+ -- __empty presence-clause list means the key is optional__, not that it is+ -- unconstrained; and an __empty allowed-value list means unconstrained__,+ -- not that no value is permitted.+ EffectiveFieldRule,+ PresenceClause,+ FieldRequirement (..),+ fieldRulePresenceClauses,+ presenceClauseRequirement,+ presenceClauseCondition,+ fieldRuleDescription,+ fieldRuleAllowedValues,+ fieldRuleCardinality,+ fieldRuleFormat,+ fieldRuleReference,+ fieldRuleUniqueBy,+ fieldRulePath,+ fieldRuleElementFields,+ fieldRuleObjectFields,+ fieldRuleElementAllowsUnknownFields,+ fieldRuleObjectAllowsUnknownFields,+ compiledProfileTypeNames,+ compiledProfileBaseRules,+ compiledProfileRulesForType,+ renderCardinalityName,+ renderFieldFormatName,++ -- * Validation+ DocumentId (..),+ parseDocumentId,+ renderDocumentId,+ documentIdsInBundle,+ nextDocumentId,+ ProfileViolation (..),+ validateProfile,+ validateProfileWith,+ validateProfileVersion,++ -- * Body inspection+ schemaSectionColumns,+ )+where++import CMarkGFM qualified+import Control.Exception (SomeException)+import Data.Aeson (ToJSON (..), object, (.=))+import Data.Aeson qualified as Aeson+import Data.Aeson.Key qualified as Aeson.Key+import Data.Aeson.KeyMap qualified as Aeson.KeyMap+import Data.Char (isAsciiLower, isAsciiUpper)+import Data.Foldable (toList)+import Data.List qualified as List+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (catMaybes, mapMaybe, maybeToList)+import Data.Set qualified as Set+import Data.Text qualified as Text+import Data.Text.Read qualified as Text.Read+import Data.Time.Calendar (Day)+import Data.Time.Clock (UTCTime)+import Data.Time.Format.ISO8601 (iso8601ParseM)+import Data.Vector qualified as Vector+import Data.Void (Void)+import Dhall (FromDhall (..), auto, genericAutoWith)+import Dhall qualified+import Dhall.Core (Expr)+import Dhall.Core qualified as Dhall.Core+import Dhall.Map qualified as Dhall.Map+import Dhall.Src (Src)+import Network.URI (parseURI, uriScheme)+import Numeric.Natural (Natural)+import Okf.Exception (catchSynchronous)++-- Imported qualified: 'Okf.Actor.HumanActor' is a constructor of 'Actor.Actor'+-- and 'HumanActor' is a constructor of 'FieldFormat', so an unqualified import+-- makes the two ambiguous.+import Okf.Actor qualified as Actor+import Okf.Bundle+ ( BundleInventory,+ Concept,+ bundleInventoryMember,+ bundleInventoryOfConcepts,+ conceptDocument,+ conceptIdOf,+ conceptResource,+ conceptType,+ )+import Okf.ConceptId (ConceptId, conceptIdFromFilePath, renderConceptId)+import Okf.Document+ ( Frontmatter,+ coreFrontmatterFields,+ fieldsSupersededInV02,+ frontmatterKeys,+ frontmatterLookup,+ )+import Okf.Index+ ( OkfVersion (..),+ VersionDeclaration (..),+ parseOkfVersion,+ renderOkfVersion,+ supportedOkfVersion,+ )+import Okf.Markdown (markdownOptions)+import Okf.Path (PathReference (..), classifyPathReference)++-- 'List' and 'Object' are 'Cardinality' constructors here; the names aeson uses+-- for the corresponding 'Value' constructors are reached as 'Aeson.Array' and+-- 'Aeson.Object'.+import Okf.Prelude hiding (List, Object, (.=))+import Okf.Validation (ValidationProfile (..))+import System.FilePath qualified as FilePath+import Text.Regex.TDFA (Regex, defaultCompOpt, defaultExecOpt)+import Text.Regex.TDFA.Text qualified as Regex.Text+import "generic-lens" Data.Generics.Labels ()++-- | A complete house profile. @description@ concisely documents the profile's+-- identity. @guidance@ is multiline Markdown prescribing how authors should do+-- the work. Neither is checked against a bundle or executed, and neither can+-- produce a 'ProfileViolation'.+--+-- @requireBundleVersion@ and @okfVersion@ are easy to confuse and answer+-- different questions. @okfVersion@ says which version's rules /this profile+-- writes/, and 'compileProfile' checks the profile's own rules against it.+-- @requireBundleVersion@ says what the profile demands of a /bundle/: @Just+-- \"0.2\"@ means the bundle's root @index.md@ must declare @okf_version@ at 0.2+-- or later, checked by 'validateProfileVersion'. Neither constrains the other.+data ProfileSpec = ProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !FrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ requireBundleVersion :: !(Maybe Text),+ types :: ![TypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | Frontmatter keys the profile expects on every concept. @required@ is always+-- checked, @recommended@ only under 'StrictAuthoring', and @optional@ never:+-- an optional key is fully validated whenever it is present and its absence is+-- never a deviation in any mode.+data FrontmatterRules = FrontmatterRules+ { required :: ![FieldRule],+ recommended :: ![FieldRule],+ optional :: ![FieldRule],+ presenceConstraints :: ![PresenceConstraint],+ timestampOrderConstraints :: ![TimestampOrderConstraint],+ membershipConstraints :: ![MembershipConstraint],+ uniqueValuesConstraints :: ![UniqueValuesConstraint]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | A same-scope predicate controlling whether a field's presence rule applies.+-- The source field must compile to a closed scalar textual vocabulary.+data FieldCondition = FieldCondition+ { field :: !Text,+ hasValue :: ![Text]+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | A top-level field whose textual values point either to a local document+-- handle with one prefix or to an absolute URI with an explicitly allowed+-- scheme. External targets are never resolved by okf.+data HandleReferenceRule = HandleReferenceRule+ { localPrefix :: !Text,+ externalUriSchemes :: ![Text],+ allowSelf :: !Bool,+ allowLocal :: !Bool,+ externalUriPattern :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | A field whose values name a path or URI per OKF v0.2 specification §6.2: an+-- absolute URL, a bundle-relative path beginning with @\/@, or an ordinary+-- relative path resolved against the concept's own directory.+--+-- Deliberately distinct from 'HandleReferenceRule'. A handle resolves against+-- the bundle's document-ID index and fails by carrying the wrong prefix or+-- having no owner; a path resolves against the concept tree and fails by not+-- being a §6.2 shape, by climbing above the bundle root, or by naming a concept+-- that is not there. Declaring both on one rule is a definition error.+--+-- An empty @externalUriSchemes@ means no absolute URL is permitted at all, so a+-- separate "must be a path, never a URL" flag would say nothing new. Which+-- bundle paths okf can resolve depends on the entry point: 'validateProfileWith'+-- resolves every file the bundle holds, including §6.3's+-- @references\/attesters\/revenue.py@, while 'validateProfile' is handed+-- concepts alone and so resolves @.md@ targets only.+data PathReferenceRule = PathReferenceRule+ { externalUriSchemes :: ![Text],+ allowSelf :: !Bool+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | One documented frontmatter key. The description is prose for humans and is+-- never checked against a bundle.+--+-- @elementFields@ and @objectFields@ describe two different shapes.+-- @elementFields@ constrains the record inside /each element of a list/;+-- @objectFields@ constrains the record that /is/ the value. Declaring both means+-- either spelling is accepted and both are checked against the same member+-- rules, which is how a profile describes the OKF v0.2 @verified@ key.+data FieldRule = FieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe FieldFormat),+ elementFields :: !(Maybe NestedRules),+ objectFields :: !(Maybe NestedRules),+ reference :: !(Maybe HandleReferenceRule),+ path :: !(Maybe PathReferenceRule),+ when :: !(Maybe FieldCondition),+ uniqueBy :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | Rules for the flat object stored in each element of a list-valued field, or+-- in the mapping that is the value of an object-valued field. The nested rule+-- type has neither an @elementFields@ nor an @objectFields@ member, which makes+-- the public descriptor depth-bounded rather than recursive.+data NestedRules = NestedRules+ { allowUnknownFields :: !Bool,+ required :: ![NestedFieldRule],+ recommended :: ![NestedFieldRule],+ optional :: ![NestedFieldRule],+ presenceConstraints :: ![PresenceConstraint],+ timestampOrderConstraints :: ![TimestampOrderConstraint]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | One field inside a list element record or an object-valued mapping. It+-- carries @path@ because @sources[].resource@ is the motivating path-valued+-- field of §6.2 and lives inside a list element; it deliberately carries no+-- @reference@, because no v0.2 field names a document handle at nested scope.+data NestedFieldRule = NestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe FieldFormat),+ path :: !(Maybe PathReferenceRule),+ when :: !(Maybe FieldCondition),+ reference :: !(Maybe HandleReferenceRule)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | Whether a key must be a single value, a non-empty list, a mapping, or any+-- of them. 'Object' is placed last so the derived 'Ord' keeps the relative+-- order of the three published alternatives, which the definition-error sort key+-- relies on.+data Cardinality = Any | Scalar | List | Object+ deriving stock (Generic, Eq, Ord, Show)++-- | Decoded from the three-alternative published union in+-- @okf-core\/dhall\/Cardinality.dhall@. 'Object' is deliberately unreachable+-- from Dhall: it is produced only by compilation, when a rule declares+-- @objectFields@. Adding an alternative to the published union would change the+-- type of every value written against it and would break descriptors pinned to+-- the previous schema, which no record-level fallback decoder can repair,+-- because every frozen generation in the chain refers to this same type. An+-- author who wants to say "this key must be a mapping" and nothing more writes+-- @objectFields = Some NestedRules::{=}@.+instance FromDhall Cardinality where+ autoWith _normalizer =+ Dhall.union+ ( (Any <$ Dhall.constructor "Any" Dhall.unit)+ <> (Scalar <$ Dhall.constructor "Scalar" Dhall.unit)+ <> (List <$ Dhall.constructor "List" Dhall.unit)+ )++-- | A named value format. Formats constrain present values but do not imply+-- that a field must be present.+--+-- The first five constrain text. 'Actor' and 'HumanActor' constrain text against+-- the OKF v0.2 actor convention of specification §7, classified by+-- 'Okf.Actor.parseActor'. 'Integer', 'NonNegativeInteger', and 'Boolean'+-- constrain a value that is not text at all; declaring one of them refines an+-- unspecified cardinality to 'Scalar', because otherwise a numeric or boolean+-- key is reported missing before its value is ever examined.+--+-- The OKF v0.2 alternatives are appended after 'DocumentHandle' so the derived+-- 'Ord' keeps the relative order of the original five, which the+-- definition-error sort key relies on.+data FieldFormat+ = Rfc3339Utc+ | Date+ | Uri+ | UriWithScheme Text+ | DocumentHandle Text+ | Actor+ | HumanActor+ | Integer+ | NonNegativeInteger+ | Boolean+ | FiniteDecimal+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | One rule per allowed concept @type@ string. @guidance@ adds authoring+-- procedure specific to this type; it is documentary and never executed.+data TypeRule = TypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ frontmatter :: !FrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++-- | Decode @type_@ from the Dhall field @type@ by stripping the trailing+-- underscore; all other fields map by their exact name. (Mirrors how+-- 'Okf.Bundle' uses a @type_@ field to avoid clashing with the @type@ keyword.)+instance FromDhall TypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | Encode a profile so tooling can consume the same descriptor okf reads.+-- Written by hand rather than derived so the key order is stable and, more+-- importantly, so 'TypeRule' emits @type@ rather than the Haskell field name+-- @type_@ — matching the Dhall field and how 'Okf.Graph.Node' already encodes.+instance ToJSON ProfileSpec where+ toJSON ProfileSpec {name, description, guidance, okfVersion, frontmatter, allowUnknownTypes, allowUnknownFields, idField, requireBundleVersion, types = typeRules} =+ object+ [ "name" .= name,+ "description" .= description,+ "guidance" .= guidance,+ "okfVersion" .= okfVersion,+ "requireBundleVersion" .= requireBundleVersion,+ "allowUnknownTypes" .= allowUnknownTypes,+ "allowUnknownFields" .= allowUnknownFields,+ "idField" .= idField,+ "frontmatter" .= frontmatter,+ "types" .= typeRules+ ]++instance ToJSON FrontmatterRules where+ toJSON FrontmatterRules {required, recommended, optional, presenceConstraints, timestampOrderConstraints, membershipConstraints, uniqueValuesConstraints} =+ object+ [ "required" .= required,+ "recommended" .= recommended,+ "optional" .= optional,+ "presenceConstraints" .= presenceConstraints,+ "timestampOrderConstraints" .= timestampOrderConstraints,+ "membershipConstraints" .= membershipConstraints,+ "uniqueValuesConstraints" .= uniqueValuesConstraints+ ]++instance ToJSON FieldCondition where+ toJSON FieldCondition {field = fieldName, hasValue} =+ object+ [ "field" .= fieldName,+ "hasValue" .= hasValue+ ]++instance ToJSON HandleReferenceRule where+ toJSON HandleReferenceRule {localPrefix, externalUriSchemes, allowSelf, allowLocal, externalUriPattern} =+ object+ [ "localPrefix" .= localPrefix,+ "externalUriSchemes" .= externalUriSchemes,+ "allowSelf" .= allowSelf,+ "allowLocal" .= allowLocal,+ "externalUriPattern" .= externalUriPattern+ ]++instance ToJSON PathReferenceRule where+ toJSON PathReferenceRule {externalUriSchemes, allowSelf} =+ object+ [ "externalUriSchemes" .= externalUriSchemes,+ "allowSelf" .= allowSelf+ ]++instance ToJSON FieldRule where+ toJSON FieldRule {field = fieldName, description, allowedValues, cardinality, format, elementFields, objectFields, reference, path = pathRule, when = condition, uniqueBy} =+ object+ [ "field" .= fieldName,+ "description" .= description,+ "allowedValues" .= allowedValues,+ "cardinality" .= cardinality,+ "format" .= format,+ "elementFields" .= elementFields,+ "reference" .= reference,+ "when" .= condition,+ -- Appended rather than placed beside @elementFields@, so that the list+ -- reads as "the keys this instance has always emitted, then the ones+ -- added since". A consumer keys on names, not position.+ "objectFields" .= objectFields,+ "path" .= pathRule,+ "uniqueBy" .= uniqueBy+ ]++instance ToJSON NestedRules where+ toJSON NestedRules {required, recommended, optional, presenceConstraints, timestampOrderConstraints, allowUnknownFields} =+ object+ [ "allowUnknownFields" .= allowUnknownFields,+ "required" .= required,+ "recommended" .= recommended,+ "optional" .= optional,+ "presenceConstraints" .= presenceConstraints,+ "timestampOrderConstraints" .= timestampOrderConstraints+ ]++instance ToJSON NestedFieldRule where+ toJSON NestedFieldRule {field = fieldName, description, allowedValues, cardinality, format, path = pathRule, when = condition, reference} =+ object+ [ "field" .= fieldName,+ "description" .= description,+ "allowedValues" .= allowedValues,+ "cardinality" .= cardinality,+ "format" .= format,+ "when" .= condition,+ -- Appended for the same reason 'FieldRule' appends @objectFields@.+ "path" .= pathRule,+ "reference" .= reference+ ]++instance ToJSON Cardinality where+ toJSON = String . cardinalityName++cardinalityName :: Cardinality -> Text+cardinalityName = \case+ Any -> "any"+ Scalar -> "scalar"+ List -> "list"+ Object -> "object"++instance ToJSON FieldFormat where+ toJSON = \case+ Rfc3339Utc -> String "rfc3339-utc"+ Date -> String "date"+ Uri -> String "uri"+ UriWithScheme scheme -> object ["uriWithScheme" .= scheme]+ DocumentHandle prefix -> object ["documentHandle" .= prefix]+ Actor -> String "actor"+ HumanActor -> String "human-actor"+ Integer -> String "integer"+ NonNegativeInteger -> String "non-negative-integer"+ FiniteDecimal -> String "finite-decimal"+ Boolean -> String "boolean"++instance ToJSON TypeRule where+ toJSON+ TypeRule+ { type_ = ruleType,+ description,+ guidance,+ frontmatter,+ pathPattern,+ resourceScheme,+ requireSchemaSection,+ schemaColumns,+ idPrefix+ } =+ object+ [ "type" .= ruleType,+ "description" .= description,+ "guidance" .= guidance,+ "frontmatter" .= frontmatter,+ "pathPattern" .= pathPattern,+ "resourceScheme" .= resourceScheme,+ "requireSchemaSection" .= requireSchemaSection,+ "schemaColumns" .= schemaColumns,+ "idPrefix" .= idPrefix+ ]++-- | The complete descriptor generation published by okf-core 0.8.0.0, frozen+-- immediately before profile and type guidance were added. Every reachable+-- record and union is copied here so future schema additions cannot change the+-- Dhall type this decoder accepts. Exercised by+-- @okf-core/test/fixtures/profiles/pre-guidance-0.8.0.0.dhall@.+data PreGuidanceCardinality+ = PreGuidanceAny+ | PreGuidanceScalar+ | PreGuidanceList+ deriving stock (Generic, Eq, Ord, Show)++instance FromDhall PreGuidanceCardinality where+ autoWith _normalizer =+ Dhall.union+ ( (PreGuidanceAny <$ Dhall.constructor "Any" Dhall.unit)+ <> (PreGuidanceScalar <$ Dhall.constructor "Scalar" Dhall.unit)+ <> (PreGuidanceList <$ Dhall.constructor "List" Dhall.unit)+ )++data PreGuidanceFieldFormat+ = PreGuidanceRfc3339Utc+ | PreGuidanceDate+ | PreGuidanceUri+ | PreGuidanceUriWithScheme Text+ | PreGuidanceDocumentHandle Text+ | PreGuidanceActor+ | PreGuidanceHumanActor+ | PreGuidanceInteger+ | PreGuidanceNonNegativeInteger+ | PreGuidanceBoolean+ deriving stock (Generic, Eq, Ord, Show)++instance FromDhall PreGuidanceFieldFormat where+ autoWith _normalizer =+ Dhall.union+ ( (PreGuidanceRfc3339Utc <$ Dhall.constructor "Rfc3339Utc" Dhall.unit)+ <> (PreGuidanceDate <$ Dhall.constructor "Date" Dhall.unit)+ <> (PreGuidanceUri <$ Dhall.constructor "Uri" Dhall.unit)+ <> (PreGuidanceUriWithScheme <$> Dhall.constructor "UriWithScheme" Dhall.auto)+ <> (PreGuidanceDocumentHandle <$> Dhall.constructor "DocumentHandle" Dhall.auto)+ <> (PreGuidanceActor <$ Dhall.constructor "Actor" Dhall.unit)+ <> (PreGuidanceHumanActor <$ Dhall.constructor "HumanActor" Dhall.unit)+ <> (PreGuidanceInteger <$ Dhall.constructor "Integer" Dhall.unit)+ <> (PreGuidanceNonNegativeInteger <$ Dhall.constructor "NonNegativeInteger" Dhall.unit)+ <> (PreGuidanceBoolean <$ Dhall.constructor "Boolean" Dhall.unit)+ )++data PreGuidanceFieldCondition = PreGuidanceFieldCondition+ { field :: !Text,+ hasValue :: ![Text]+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreGuidanceHandleReferenceRule = PreGuidanceHandleReferenceRule+ { localPrefix :: !Text,+ externalUriSchemes :: ![Text],+ allowSelf :: !Bool,+ allowLocal :: !Bool,+ externalUriPattern :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreGuidancePathReferenceRule = PreGuidancePathReferenceRule+ { externalUriSchemes :: ![Text],+ allowSelf :: !Bool+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | FROZEN: complete descriptor at cfecb72e543c28f5a77ff852445fdd0f256b8810,+-- after product-discovery constraints and before nested closure. No live+-- descriptor records or unions are shared with this generation.+data PreNestedClosureCardinality+ = PreNestedClosureAny+ | PreNestedClosureScalar+ | PreNestedClosureList+ deriving stock (Generic, Eq, Ord, Show)++instance FromDhall PreNestedClosureCardinality where+ autoWith _normalizer =+ Dhall.union+ ( (PreNestedClosureAny <$ Dhall.constructor "Any" Dhall.unit)+ <> (PreNestedClosureScalar <$ Dhall.constructor "Scalar" Dhall.unit)+ <> (PreNestedClosureList <$ Dhall.constructor "List" Dhall.unit)+ )++data PreNestedClosureFieldFormat+ = PreNestedClosureRfc3339Utc+ | PreNestedClosureDate+ | PreNestedClosureUri+ | PreNestedClosureUriWithScheme Text+ | PreNestedClosureDocumentHandle Text+ | PreNestedClosureActor+ | PreNestedClosureHumanActor+ | PreNestedClosureInteger+ | PreNestedClosureNonNegativeInteger+ | PreNestedClosureFiniteDecimal+ | PreNestedClosureBoolean+ deriving stock (Generic, Eq, Ord, Show)++instance FromDhall PreNestedClosureFieldFormat where+ autoWith _normalizer =+ Dhall.union+ ( (PreNestedClosureRfc3339Utc <$ Dhall.constructor "Rfc3339Utc" Dhall.unit)+ <> (PreNestedClosureDate <$ Dhall.constructor "Date" Dhall.unit)+ <> (PreNestedClosureUri <$ Dhall.constructor "Uri" Dhall.unit)+ <> (PreNestedClosureUriWithScheme <$> Dhall.constructor "UriWithScheme" Dhall.auto)+ <> (PreNestedClosureDocumentHandle <$> Dhall.constructor "DocumentHandle" Dhall.auto)+ <> (PreNestedClosureActor <$ Dhall.constructor "Actor" Dhall.unit)+ <> (PreNestedClosureHumanActor <$ Dhall.constructor "HumanActor" Dhall.unit)+ <> (PreNestedClosureInteger <$ Dhall.constructor "Integer" Dhall.unit)+ <> (PreNestedClosureNonNegativeInteger <$ Dhall.constructor "NonNegativeInteger" Dhall.unit)+ <> (PreNestedClosureFiniteDecimal <$ Dhall.constructor "FiniteDecimal" Dhall.unit)+ <> (PreNestedClosureBoolean <$ Dhall.constructor "Boolean" Dhall.unit)+ )++data PreNestedClosureProfileSpec = PreNestedClosureProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreNestedClosureFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ requireBundleVersion :: !(Maybe Text),+ types :: ![PreNestedClosureTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureTypeRule = PreNestedClosureTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ frontmatter :: !PreNestedClosureFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreNestedClosureTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++data PreNestedClosureFrontmatterRules = PreNestedClosureFrontmatterRules+ { required :: ![PreNestedClosureFieldRule],+ recommended :: ![PreNestedClosureFieldRule],+ optional :: ![PreNestedClosureFieldRule],+ presenceConstraints :: ![PreNestedClosurePresenceConstraint],+ timestampOrderConstraints :: ![PreNestedClosureTimestampOrderConstraint],+ membershipConstraints :: ![PreNestedClosureMembershipConstraint],+ uniqueValuesConstraints :: ![PreNestedClosureUniqueValuesConstraint]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureFieldRule = PreNestedClosureFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreNestedClosureCardinality,+ format :: !(Maybe PreNestedClosureFieldFormat),+ elementFields :: !(Maybe PreNestedClosureNestedRules),+ objectFields :: !(Maybe PreNestedClosureNestedRules),+ reference :: !(Maybe PreNestedClosureHandleReferenceRule),+ path :: !(Maybe PreNestedClosurePathReferenceRule),+ when :: !(Maybe PreNestedClosureFieldCondition),+ uniqueBy :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureNestedRules = PreNestedClosureNestedRules+ { required :: ![PreNestedClosureNestedFieldRule],+ recommended :: ![PreNestedClosureNestedFieldRule],+ optional :: ![PreNestedClosureNestedFieldRule],+ presenceConstraints :: ![PreNestedClosurePresenceConstraint],+ timestampOrderConstraints :: ![PreNestedClosureTimestampOrderConstraint]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureNestedFieldRule = PreNestedClosureNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreNestedClosureCardinality,+ format :: !(Maybe PreNestedClosureFieldFormat),+ path :: !(Maybe PreNestedClosurePathReferenceRule),+ when :: !(Maybe PreNestedClosureFieldCondition),+ reference :: !(Maybe PreNestedClosureHandleReferenceRule)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureFieldCondition = PreNestedClosureFieldCondition+ { field :: !Text,+ hasValue :: ![Text]+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureHandleReferenceRule = PreNestedClosureHandleReferenceRule+ { localPrefix :: !Text,+ externalUriSchemes :: ![Text],+ allowSelf :: !Bool,+ allowLocal :: !Bool,+ externalUriPattern :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosurePathReferenceRule = PreNestedClosurePathReferenceRule+ { externalUriSchemes :: ![Text],+ allowSelf :: !Bool+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureValueSelector = PreNestedClosureValueSelector {field :: !Text, member :: !(Maybe Text)}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosurePresenceConstraint = PreNestedClosurePresenceConstraint+ { when :: !(Maybe PreNestedClosureFieldCondition),+ whenAnyPresent :: ![PreNestedClosureValueSelector],+ requireAll :: ![PreNestedClosureValueSelector],+ requireAny :: ![PreNestedClosureValueSelector]+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureTimestampOrderConstraint = PreNestedClosureTimestampOrderConstraint {before :: !Text, after :: !Text}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedClosureMembershipConstraint = PreNestedClosureMembershipConstraint {source :: !PreNestedClosureValueSelector, target :: !PreNestedClosureValueSelector}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++newtype PreNestedClosureUniqueValuesConstraint = PreNestedClosureUniqueValuesConstraint {selectors :: [PreNestedClosureValueSelector]}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++-- | FROZEN: complete public 0.10.0.0 generation, before concept constraints+-- and FiniteDecimal. Only unions and policies already frozen above are shared.+data PreProductDiscoveryFieldRule = PreProductDiscoveryFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreGuidanceCardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ elementFields :: !(Maybe PreProductDiscoveryNestedRules),+ objectFields :: !(Maybe PreProductDiscoveryNestedRules),+ reference :: !(Maybe PreGuidanceHandleReferenceRule),+ path :: !(Maybe PreGuidancePathReferenceRule),+ when :: !(Maybe PreGuidanceFieldCondition),+ uniqueBy :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreProductDiscoveryNestedRules = PreProductDiscoveryNestedRules+ { required :: ![PreProductDiscoveryNestedFieldRule],+ recommended :: ![PreProductDiscoveryNestedFieldRule],+ optional :: ![PreProductDiscoveryNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreProductDiscoveryNestedFieldRule = PreProductDiscoveryNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreGuidanceCardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ path :: !(Maybe PreGuidancePathReferenceRule),+ when :: !(Maybe PreGuidanceFieldCondition),+ reference :: !(Maybe PreGuidanceHandleReferenceRule)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreProductDiscoveryFrontmatterRules = PreProductDiscoveryFrontmatterRules+ { required :: ![PreProductDiscoveryFieldRule],+ recommended :: ![PreProductDiscoveryFieldRule],+ optional :: ![PreProductDiscoveryFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreProductDiscoveryTypeRule = PreProductDiscoveryTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ frontmatter :: !PreProductDiscoveryFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreProductDiscoveryTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++data PreProductDiscoveryProfileSpec = PreProductDiscoveryProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ guidance :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreProductDiscoveryFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ requireBundleVersion :: !(Maybe Text),+ types :: ![PreProductDiscoveryTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreGuidanceFieldRule = PreGuidanceFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreGuidanceCardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ elementFields :: !(Maybe PreGuidanceNestedRules),+ objectFields :: !(Maybe PreGuidanceNestedRules),+ reference :: !(Maybe PreGuidanceHandleReferenceRule),+ path :: !(Maybe PreGuidancePathReferenceRule),+ when :: !(Maybe PreGuidanceFieldCondition),+ uniqueBy :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreGuidanceNestedRules = PreGuidanceNestedRules+ { required :: ![PreGuidanceNestedFieldRule],+ recommended :: ![PreGuidanceNestedFieldRule],+ optional :: ![PreGuidanceNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreGuidanceNestedFieldRule = PreGuidanceNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !PreGuidanceCardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ path :: !(Maybe PreGuidancePathReferenceRule),+ when :: !(Maybe PreGuidanceFieldCondition),+ reference :: !(Maybe PreGuidanceHandleReferenceRule)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreGuidanceFrontmatterRules = PreGuidanceFrontmatterRules+ { required :: ![PreGuidanceFieldRule],+ recommended :: ![PreGuidanceFieldRule],+ optional :: ![PreGuidanceFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreGuidanceTypeRule = PreGuidanceTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PreGuidanceFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreGuidanceTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++data PreGuidanceProfileSpec = PreGuidanceProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreGuidanceFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ requireBundleVersion :: !(Maybe Text),+ types :: ![PreGuidanceTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The okf 0.2.x profile record: frontmatter keys were bare strings and+-- nothing carried a description. Decoded only as a fallback, so descriptors+-- written before descriptions existed keep loading unchanged. Deliberately+-- private and deliberately frozen — it is a record of a retired shape, not a+-- second profile model. Exercised by+-- @okf-core\/test\/fixtures\/profiles\/legacy-0.2.dhall@.+data LegacyProfileSpec = LegacyProfileSpec+ { name :: !Text,+ okfVersion :: !Text,+ frontmatter :: !LegacyFrontmatterRules,+ allowUnknownTypes :: !Bool,+ idField :: !(Maybe Text),+ types :: ![LegacyTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The okf 0.2.x frontmatter record: two lists of bare key names.+data LegacyFrontmatterRules = LegacyFrontmatterRules+ { required :: ![Text],+ recommended :: ![Text]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The okf 0.2.x per-@type@ rule: today's 'TypeRule' without descriptions or+-- type-specific frontmatter.+data LegacyTypeRule = LegacyTypeRule+ { type_ :: !Text,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall LegacyTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The five-alternative published format union, frozen before the OKF v0.2+-- value formats were added.+--+-- Unlike every other frozen shape below this is a /union/ rather than a record.+-- A Dhall union value carries its full alternative set in its type, so a+-- descriptor pinned to the five-alternative+-- @okf-core\/dhall\/FieldFormat.dhall@ does not typecheck against the current+-- decoder, and no record-level fallback can repair that: every frozen generation+-- would still refer to the widened 'FieldFormat'. Every frozen generation below+-- therefore refers to this type instead.+--+-- The instance is hand-written rather than derived so that the Dhall alternative+-- names stay @Rfc3339Utc@ and friends while the Haskell constructors stay+-- distinct from the current type's.+data PreV02FieldFormat+ = LegacyRfc3339Utc+ | LegacyDate+ | LegacyUri+ | LegacyUriWithScheme Text+ | LegacyDocumentHandle Text+ deriving stock (Generic, Eq, Ord, Show)++instance FromDhall PreV02FieldFormat where+ autoWith _normalizer =+ Dhall.union+ ( (LegacyRfc3339Utc <$ Dhall.constructor "Rfc3339Utc" Dhall.unit)+ <> (LegacyDate <$ Dhall.constructor "Date" Dhall.unit)+ <> (LegacyUri <$ Dhall.constructor "Uri" Dhall.unit)+ <> (LegacyUriWithScheme <$> Dhall.constructor "UriWithScheme" Dhall.auto)+ <> (LegacyDocumentHandle <$> Dhall.constructor "DocumentHandle" Dhall.auto)+ )++upgradePreV02FieldFormat :: PreV02FieldFormat -> FieldFormat+upgradePreV02FieldFormat = \case+ LegacyRfc3339Utc -> Rfc3339Utc+ LegacyDate -> Date+ LegacyUri -> Uri+ LegacyUriWithScheme scheme -> UriWithScheme scheme+ LegacyDocumentHandle prefix -> DocumentHandle prefix++-- | The complete 0.7.0.0 descriptor generation, frozen before nested+-- document-reference policies and record-list uniqueness were added. Every+-- record that directly or transitively contains one of the grown records is+-- copied so a descriptor pinned to 0.7.0.0 remains decodable as one closed+-- Dhall type.+data PreNestedReferenceHandleReferenceRule = PreNestedReferenceHandleReferenceRule+ { localPrefix :: !Text,+ externalUriSchemes :: ![Text],+ allowSelf :: !Bool+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceFieldRule = PreNestedReferenceFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ elementFields :: !(Maybe PreNestedReferenceNestedRules),+ objectFields :: !(Maybe PreNestedReferenceNestedRules),+ reference :: !(Maybe PreNestedReferenceHandleReferenceRule),+ path :: !(Maybe PathReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceNestedRules = PreNestedReferenceNestedRules+ { required :: ![PreNestedReferenceNestedFieldRule],+ recommended :: ![PreNestedReferenceNestedFieldRule],+ optional :: ![PreNestedReferenceNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceNestedFieldRule = PreNestedReferenceNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ path :: !(Maybe PathReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceFrontmatterRules = PreNestedReferenceFrontmatterRules+ { required :: ![PreNestedReferenceFieldRule],+ recommended :: ![PreNestedReferenceFieldRule],+ optional :: ![PreNestedReferenceFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceProfileSpec = PreNestedReferenceProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreNestedReferenceFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ requireBundleVersion :: !(Maybe Text),+ types :: ![PreNestedReferenceTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreNestedReferenceTypeRule = PreNestedReferenceTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PreNestedReferenceFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreNestedReferenceTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete descriptor generation frozen before a profile could require its+-- bundle to declare an OKF version. This is the immediately preceding public+-- descriptor generation: it is today's shape minus the @requireBundleVersion@+-- member on the top-level record. That member is the only difference, so every+-- rule record, every type rule, and every union is unchanged and is shared+-- rather than copied — the freezing rule of+-- @docs\/adr\/11-growing-the-profile-descriptor-language.md@ is about the+-- descriptor as a whole, and a generation that changes one record copies only+-- what changed. Exercised by+-- @okf-core\/test\/fixtures\/profiles\/pre-bundle-version.dhall@.+data PreBundleVersionProfileSpec = PreBundleVersionProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreNestedReferenceFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![PreNestedReferenceTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The complete descriptor generation frozen before path-valued reference+-- rules were added: today's shape minus the @path@ member on 'FieldRule' and on+-- 'NestedFieldRule'. Because that is a record addition rather than a union+-- widening, 'Cardinality', 'FieldFormat', 'FieldCondition', and+-- 'HandleReferenceRule' are unchanged by it and so are shared rather than+-- copied; the two rule records and everything that contains them are copied.+-- Exercised by @okf-core\/test\/fixtures\/profiles\/path-references-mp8-ep3.dhall@.+data PrePathProfileFieldRule = PrePathProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ elementFields :: !(Maybe PrePathProfileNestedRules),+ objectFields :: !(Maybe PrePathProfileNestedRules),+ reference :: !(Maybe PreNestedReferenceHandleReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PrePathProfileNestedRules = PrePathProfileNestedRules+ { required :: ![PrePathProfileNestedFieldRule],+ recommended :: ![PrePathProfileNestedFieldRule],+ optional :: ![PrePathProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PrePathProfileNestedFieldRule = PrePathProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreGuidanceFieldFormat),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PrePathProfileFrontmatterRules = PrePathProfileFrontmatterRules+ { required :: ![PrePathProfileFieldRule],+ recommended :: ![PrePathProfileFieldRule],+ optional :: ![PrePathProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PrePathProfileSpec = PrePathProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PrePathProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![PrePathProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PrePathProfileTypeRule = PrePathProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PrePathProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PrePathProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete object-rule descriptor generation, frozen before the OKF v0.2+-- value formats were added to 'FieldFormat'. Its records match the shape+-- 'PrePathProfileFieldRule' froze — no @path@ member — and the further+-- difference is that every @format@ member refers to the frozen+-- five-alternative 'PreV02FieldFormat'. Exercised by+-- @okf-core\/test\/fixtures\/profiles\/formats-mp8-ep2.dhall@.+data PreActorProfileFieldRule = PreActorProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ elementFields :: !(Maybe PreActorProfileNestedRules),+ objectFields :: !(Maybe PreActorProfileNestedRules),+ reference :: !(Maybe PreNestedReferenceHandleReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreActorProfileNestedRules = PreActorProfileNestedRules+ { required :: ![PreActorProfileNestedFieldRule],+ recommended :: ![PreActorProfileNestedFieldRule],+ optional :: ![PreActorProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreActorProfileNestedFieldRule = PreActorProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreActorProfileFrontmatterRules = PreActorProfileFrontmatterRules+ { required :: ![PreActorProfileFieldRule],+ recommended :: ![PreActorProfileFieldRule],+ optional :: ![PreActorProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreActorProfileSpec = PreActorProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreActorProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![PreActorProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreActorProfileTypeRule = PreActorProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PreActorProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreActorProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete optional-presence descriptor generation, frozen before object+-- rules were added: it matches the shape before @objectFields@ was added to+-- 'FieldRule'. 'Cardinality', 'HandleReferenceRule', and 'FieldCondition' are+-- unchanged by every addition since and so are shared rather than copied; the+-- nested rule types are copied because their @format@ member now refers to the+-- frozen 'PreV02FieldFormat'. Exercised by+-- @okf-core\/test\/fixtures\/profiles\/object-fields-mp8-ep1.dhall@.+data PreObjectProfileFieldRule = PreObjectProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ elementFields :: !(Maybe PreObjectProfileNestedRules),+ reference :: !(Maybe PreNestedReferenceHandleReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreObjectProfileNestedRules = PreObjectProfileNestedRules+ { required :: ![PreObjectProfileNestedFieldRule],+ recommended :: ![PreObjectProfileNestedFieldRule],+ optional :: ![PreObjectProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreObjectProfileNestedFieldRule = PreObjectProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreObjectProfileFrontmatterRules = PreObjectProfileFrontmatterRules+ { required :: ![PreObjectProfileFieldRule],+ recommended :: ![PreObjectProfileFieldRule],+ optional :: ![PreObjectProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreObjectProfileSpec = PreObjectProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreObjectProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![PreObjectProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreObjectProfileTypeRule = PreObjectProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PreObjectProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall PreObjectProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete reference-aware descriptor generation, frozen before the+-- @optional@ presence list was added to 'FrontmatterRules' and 'NestedRules'.+-- This is the immediately preceding public descriptor generation: it matches+-- today's shape exactly apart from that third list, so every descriptor written+-- as a record literal against the published schema keeps loading. Exercised by+-- @okf-core\/test\/fixtures\/profiles\/document-references-ep3.dhall@.+data ReferenceProfileFieldRule = ReferenceProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ elementFields :: !(Maybe ReferenceProfileNestedRules),+ reference :: !(Maybe PreNestedReferenceHandleReferenceRule),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ReferenceProfileNestedRules = ReferenceProfileNestedRules+ { required :: ![ReferenceProfileNestedFieldRule],+ recommended :: ![ReferenceProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ReferenceProfileNestedFieldRule = ReferenceProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ReferenceProfileFrontmatterRules = ReferenceProfileFrontmatterRules+ { required :: ![ReferenceProfileFieldRule],+ recommended :: ![ReferenceProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ReferenceProfileSpec = ReferenceProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !ReferenceProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![ReferenceProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ReferenceProfileTypeRule = ReferenceProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !ReferenceProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall ReferenceProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete condition-aware descriptor generation, frozen before+-- top-level document-reference policies were added.+data ConditionalProfileFieldRule = ConditionalProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ elementFields :: !(Maybe ConditionalProfileNestedRules),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ConditionalProfileNestedRules = ConditionalProfileNestedRules+ { required :: ![ConditionalProfileNestedFieldRule],+ recommended :: ![ConditionalProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ConditionalProfileNestedFieldRule = ConditionalProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ when :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ConditionalProfileFrontmatterRules = ConditionalProfileFrontmatterRules+ { required :: ![ConditionalProfileFieldRule],+ recommended :: ![ConditionalProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ConditionalProfileSpec = ConditionalProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !ConditionalProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![ConditionalProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data ConditionalProfileTypeRule = ConditionalProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !ConditionalProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall ConditionalProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete bounded-nested descriptor generation, frozen before+-- same-scope field conditions were added. This is the immediately preceding+-- public descriptor generation.+data NestedProfileFieldRule = NestedProfileFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat),+ elementFields :: !(Maybe NestedProfileRules)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data NestedProfileRules = NestedProfileRules+ { required :: ![NestedProfileNestedFieldRule],+ recommended :: ![NestedProfileNestedFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data NestedProfileNestedFieldRule = NestedProfileNestedFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data NestedProfileFrontmatterRules = NestedProfileFrontmatterRules+ { required :: ![NestedProfileFieldRule],+ recommended :: ![NestedProfileFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data NestedProfileSpec = NestedProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !NestedProfileFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![NestedProfileTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data NestedProfileTypeRule = NestedProfileTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !NestedProfileFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall NestedProfileTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The complete EP-4 field rule, frozen before one-level nested records were+-- added. This is the immediately preceding public descriptor generation.+data FormatFieldRule = FormatFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe PreV02FieldFormat)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data FormatFrontmatterRules = FormatFrontmatterRules+ { required :: ![FormatFieldRule],+ recommended :: ![FormatFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data FormatProfileSpec = FormatProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !FormatFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![FormatTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data FormatTypeRule = FormatTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !FormatFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall FormatTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The EP-3 field rule, frozen before named formats were added.+data CardinalityFieldRule = CardinalityFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data CardinalityFrontmatterRules = CardinalityFrontmatterRules+ { required :: ![CardinalityFieldRule],+ recommended :: ![CardinalityFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The complete EP-3 profile shape, frozen before named formats were added.+data CardinalityProfileSpec = CardinalityProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !CardinalityFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![CardinalityTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data CardinalityTypeRule = CardinalityTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !CardinalityFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall CardinalityTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The EP-2 field rule, frozen before cardinality was added.+data VocabularyFieldRule = VocabularyFieldRule+ { field :: !Text,+ description :: !(Maybe Text),+ allowedValues :: ![Text]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data VocabularyFrontmatterRules = VocabularyFrontmatterRules+ { required :: ![VocabularyFieldRule],+ recommended :: ![VocabularyFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The EP-2 profile shape, frozen before cardinality was added.+data VocabularyProfileSpec = VocabularyProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !VocabularyFrontmatterRules,+ allowUnknownTypes :: !Bool,+ allowUnknownFields :: !Bool,+ idField :: !(Maybe Text),+ types :: ![VocabularyTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data VocabularyTypeRule = VocabularyTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !VocabularyFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall VocabularyTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | A field rule from either self-documenting schema generation, before value+-- vocabularies were added.+data PreviousFieldRule = PreviousFieldRule+ { field :: !Text,+ description :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data PreviousFrontmatterRules = PreviousFrontmatterRules+ { required :: ![PreviousFieldRule],+ recommended :: ![PreviousFieldRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++-- | The type-aware shape from EP-1, frozen before vocabularies and field-name+-- closure were added.+data TypeAwareProfileSpec = TypeAwareProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreviousFrontmatterRules,+ allowUnknownTypes :: !Bool,+ idField :: !(Maybe Text),+ types :: ![TypeAwareTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data TypeAwareTypeRule = TypeAwareTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ frontmatter :: !PreviousFrontmatterRules,+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall TypeAwareTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++-- | The self-documenting profile shape published immediately before+-- type-specific frontmatter rules. It is frozen as a compatibility decoder in+-- exactly the same way as the older 0.2.x shape below.+data DescribedProfileSpec = DescribedProfileSpec+ { name :: !Text,+ description :: !(Maybe Text),+ okfVersion :: !Text,+ frontmatter :: !PreviousFrontmatterRules,+ allowUnknownTypes :: !Bool,+ idField :: !(Maybe Text),+ types :: ![DescribedTypeRule]+ }+ deriving stock (Generic, Eq, Show)+ deriving anyclass (FromDhall)++data DescribedTypeRule = DescribedTypeRule+ { type_ :: !Text,+ description :: !(Maybe Text),+ pathPattern :: !(Maybe Text),+ resourceScheme :: !(Maybe Text),+ requireSchemaSection :: !Bool,+ schemaColumns :: ![Text],+ idPrefix :: !(Maybe Text)+ }+ deriving stock (Generic, Eq, Show)++instance FromDhall DescribedTypeRule where+ autoWith _normalizer =+ genericAutoWith+ (Dhall.defaultInterpretOptions {Dhall.fieldModifier = stripTrailingUnderscore})+ where+ stripTrailingUnderscore fieldName =+ fromMaybe fieldName (Text.stripSuffix "_" fieldName)++emptyFrontmatterRules :: FrontmatterRules+emptyFrontmatterRules = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [], optional = []}++upgradePreGuidanceCardinality :: PreGuidanceCardinality -> Cardinality+upgradePreGuidanceCardinality = \case+ PreGuidanceAny -> Any+ PreGuidanceScalar -> Scalar+ PreGuidanceList -> List++upgradePreGuidanceFieldFormat :: PreGuidanceFieldFormat -> FieldFormat+upgradePreGuidanceFieldFormat = \case+ PreGuidanceRfc3339Utc -> Rfc3339Utc+ PreGuidanceDate -> Date+ PreGuidanceUri -> Uri+ PreGuidanceUriWithScheme scheme -> UriWithScheme scheme+ PreGuidanceDocumentHandle prefix -> DocumentHandle prefix+ PreGuidanceActor -> Actor+ PreGuidanceHumanActor -> HumanActor+ PreGuidanceInteger -> Integer+ PreGuidanceNonNegativeInteger -> NonNegativeInteger+ PreGuidanceBoolean -> Boolean++upgradePreGuidanceCondition :: PreGuidanceFieldCondition -> FieldCondition+upgradePreGuidanceCondition previous =+ FieldCondition+ { field = previous ^. #field,+ hasValue = previous ^. #hasValue+ }++upgradePreGuidanceHandleRule :: PreGuidanceHandleReferenceRule -> HandleReferenceRule+upgradePreGuidanceHandleRule previous =+ HandleReferenceRule+ { localPrefix = previous ^. #localPrefix,+ externalUriSchemes = previous ^. #externalUriSchemes,+ allowSelf = previous ^. #allowSelf,+ allowLocal = previous ^. #allowLocal,+ externalUriPattern = previous ^. #externalUriPattern+ }++upgradePreGuidancePathRule :: PreGuidancePathReferenceRule -> PathReferenceRule+upgradePreGuidancePathRule previous =+ PathReferenceRule+ { externalUriSchemes = previous ^. #externalUriSchemes,+ allowSelf = previous ^. #allowSelf+ }++upgradePreNestedClosureProfileSpec :: PreNestedClosureProfileSpec -> ProfileSpec+upgradePreNestedClosureProfileSpec previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = previous ^. #guidance,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreNestedClosureFrontmatterRules (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = previous ^. #requireBundleVersion,+ types = map upgradePreNestedClosureTypeRule (previous ^. #types)+ }++upgradePreNestedClosureTypeRule :: PreNestedClosureTypeRule -> TypeRule+upgradePreNestedClosureTypeRule previous =+ TypeRule+ { type_ = previous ^. #type_,+ description = previous ^. #description,+ guidance = previous ^. #guidance,+ frontmatter = upgradePreNestedClosureFrontmatterRules (previous ^. #frontmatter),+ pathPattern = previous ^. #pathPattern,+ resourceScheme = previous ^. #resourceScheme,+ requireSchemaSection = previous ^. #requireSchemaSection,+ schemaColumns = previous ^. #schemaColumns,+ idPrefix = previous ^. #idPrefix+ }++upgradePreNestedClosureFrontmatterRules :: PreNestedClosureFrontmatterRules -> FrontmatterRules+upgradePreNestedClosureFrontmatterRules previous =+ FrontmatterRules+ { required = map upgradePreNestedClosureFieldRule (previous ^. #required),+ recommended = map upgradePreNestedClosureFieldRule (previous ^. #recommended),+ optional = map upgradePreNestedClosureFieldRule (previous ^. #optional),+ presenceConstraints = map upgradePreNestedClosurePresenceConstraint (previous ^. #presenceConstraints),+ timestampOrderConstraints = map upgradePreNestedClosureTimestampOrderConstraint (previous ^. #timestampOrderConstraints),+ membershipConstraints = map upgradePreNestedClosureMembershipConstraint (previous ^. #membershipConstraints),+ uniqueValuesConstraints = map upgradePreNestedClosureUniqueValuesConstraint (previous ^. #uniqueValuesConstraints)+ }++upgradePreNestedClosureFieldRule :: PreNestedClosureFieldRule -> FieldRule+upgradePreNestedClosureFieldRule previous =+ FieldRule+ { field = previous ^. #field,+ description = previous ^. #description,+ allowedValues = previous ^. #allowedValues,+ cardinality = upgradePreNestedClosureCardinality (previous ^. #cardinality),+ format = upgradePreNestedClosureFieldFormat <$> previous ^. #format,+ elementFields = upgradePreNestedClosureNestedRules <$> previous ^. #elementFields,+ objectFields = upgradePreNestedClosureNestedRules <$> previous ^. #objectFields,+ reference = upgradePreNestedClosureHandleReferenceRule <$> previous ^. #reference,+ path = upgradePreNestedClosurePathReferenceRule <$> previous ^. #path,+ when = upgradePreNestedClosureFieldCondition <$> previous ^. #when,+ uniqueBy = previous ^. #uniqueBy+ }++upgradePreNestedClosureNestedRules :: PreNestedClosureNestedRules -> NestedRules+upgradePreNestedClosureNestedRules previous =+ NestedRules+ { allowUnknownFields = True,+ required = map upgradePreNestedClosureNestedFieldRule (previous ^. #required),+ recommended = map upgradePreNestedClosureNestedFieldRule (previous ^. #recommended),+ optional = map upgradePreNestedClosureNestedFieldRule (previous ^. #optional),+ presenceConstraints = map upgradePreNestedClosurePresenceConstraint (previous ^. #presenceConstraints),+ timestampOrderConstraints = map upgradePreNestedClosureTimestampOrderConstraint (previous ^. #timestampOrderConstraints)+ }++upgradePreNestedClosureNestedFieldRule :: PreNestedClosureNestedFieldRule -> NestedFieldRule+upgradePreNestedClosureNestedFieldRule previous =+ NestedFieldRule+ { field = previous ^. #field,+ description = previous ^. #description,+ allowedValues = previous ^. #allowedValues,+ cardinality = upgradePreNestedClosureCardinality (previous ^. #cardinality),+ format = upgradePreNestedClosureFieldFormat <$> previous ^. #format,+ path = upgradePreNestedClosurePathReferenceRule <$> previous ^. #path,+ when = upgradePreNestedClosureFieldCondition <$> previous ^. #when,+ reference = upgradePreNestedClosureHandleReferenceRule <$> previous ^. #reference+ }++upgradePreNestedClosureFieldCondition :: PreNestedClosureFieldCondition -> FieldCondition+upgradePreNestedClosureFieldCondition previous =+ FieldCondition+ { field = previous ^. #field,+ hasValue = previous ^. #hasValue+ }++upgradePreNestedClosureHandleReferenceRule :: PreNestedClosureHandleReferenceRule -> HandleReferenceRule+upgradePreNestedClosureHandleReferenceRule previous =+ HandleReferenceRule+ { localPrefix = previous ^. #localPrefix,+ externalUriSchemes = previous ^. #externalUriSchemes,+ allowSelf = previous ^. #allowSelf,+ allowLocal = previous ^. #allowLocal,+ externalUriPattern = previous ^. #externalUriPattern+ }++upgradePreNestedClosurePathReferenceRule :: PreNestedClosurePathReferenceRule -> PathReferenceRule+upgradePreNestedClosurePathReferenceRule previous =+ PathReferenceRule+ { externalUriSchemes = previous ^. #externalUriSchemes,+ allowSelf = previous ^. #allowSelf+ }++upgradePreNestedClosureValueSelector :: PreNestedClosureValueSelector -> ValueSelector+upgradePreNestedClosureValueSelector previous =+ ValueSelector+ { field = previous ^. #field,+ member = previous ^. #member+ }++upgradePreNestedClosurePresenceConstraint :: PreNestedClosurePresenceConstraint -> PresenceConstraint+upgradePreNestedClosurePresenceConstraint previous =+ PresenceConstraint+ { when = upgradePreNestedClosureFieldCondition <$> previous ^. #when,+ whenAnyPresent = map upgradePreNestedClosureValueSelector (previous ^. #whenAnyPresent),+ requireAll = map upgradePreNestedClosureValueSelector (previous ^. #requireAll),+ requireAny = map upgradePreNestedClosureValueSelector (previous ^. #requireAny)+ }++upgradePreNestedClosureTimestampOrderConstraint :: PreNestedClosureTimestampOrderConstraint -> TimestampOrderConstraint+upgradePreNestedClosureTimestampOrderConstraint previous =+ TimestampOrderConstraint+ { before = previous ^. #before,+ after = previous ^. #after+ }++upgradePreNestedClosureMembershipConstraint :: PreNestedClosureMembershipConstraint -> MembershipConstraint+upgradePreNestedClosureMembershipConstraint previous =+ MembershipConstraint+ { source = upgradePreNestedClosureValueSelector (previous ^. #source),+ target = upgradePreNestedClosureValueSelector (previous ^. #target)+ }++upgradePreNestedClosureUniqueValuesConstraint :: PreNestedClosureUniqueValuesConstraint -> UniqueValuesConstraint+upgradePreNestedClosureUniqueValuesConstraint previous =+ UniqueValuesConstraint+ { selectors = map upgradePreNestedClosureValueSelector (previous ^. #selectors)+ }++upgradePreNestedClosureCardinality :: PreNestedClosureCardinality -> Cardinality+upgradePreNestedClosureCardinality = \case+ PreNestedClosureAny -> Any+ PreNestedClosureScalar -> Scalar+ PreNestedClosureList -> List++upgradePreNestedClosureFieldFormat :: PreNestedClosureFieldFormat -> FieldFormat+upgradePreNestedClosureFieldFormat = \case+ PreNestedClosureRfc3339Utc -> Rfc3339Utc+ PreNestedClosureDate -> Date+ PreNestedClosureUri -> Uri+ PreNestedClosureUriWithScheme value -> UriWithScheme value+ PreNestedClosureDocumentHandle value -> DocumentHandle value+ PreNestedClosureActor -> Actor+ PreNestedClosureHumanActor -> HumanActor+ PreNestedClosureInteger -> Integer+ PreNestedClosureNonNegativeInteger -> NonNegativeInteger+ PreNestedClosureBoolean -> Boolean+ PreNestedClosureFiniteDecimal -> FiniteDecimal++upgradePreProductDiscoveryFrontmatter :: PreProductDiscoveryFrontmatterRules -> FrontmatterRules+upgradePreProductDiscoveryFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = upgradeNestedRules <$> rule ^. #objectFields,+ reference = upgradePreGuidanceHandleRule <$> rule ^. #reference,+ path = upgradePreGuidancePathRule <$> rule ^. #path,+ when = upgradePreGuidanceCondition <$> rule ^. #when,+ uniqueBy = rule ^. #uniqueBy+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ path = upgradePreGuidancePathRule <$> rule ^. #path,+ when = upgradePreGuidanceCondition <$> rule ^. #when,+ reference = upgradePreGuidanceHandleRule <$> rule ^. #reference+ }++upgradePreProductDiscoveryTypeRule :: PreProductDiscoveryTypeRule -> TypeRule+upgradePreProductDiscoveryTypeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = rule ^. #guidance,+ frontmatter = upgradePreProductDiscoveryFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradePreProductDiscoveryProfile :: PreProductDiscoveryProfileSpec -> ProfileSpec+upgradePreProductDiscoveryProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = previous ^. #guidance,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreProductDiscoveryFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = previous ^. #requireBundleVersion,+ types = map upgradePreProductDiscoveryTypeRule (previous ^. #types)+ }++upgradePreGuidanceFrontmatter :: PreGuidanceFrontmatterRules -> FrontmatterRules+upgradePreGuidanceFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = upgradeNestedRules <$> rule ^. #objectFields,+ reference = upgradePreGuidanceHandleRule <$> rule ^. #reference,+ path = upgradePreGuidancePathRule <$> rule ^. #path,+ when = upgradePreGuidanceCondition <$> rule ^. #when,+ uniqueBy = rule ^. #uniqueBy+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = upgradePreGuidanceCardinality (rule ^. #cardinality),+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ path = upgradePreGuidancePathRule <$> rule ^. #path,+ when = upgradePreGuidanceCondition <$> rule ^. #when,+ reference = upgradePreGuidanceHandleRule <$> rule ^. #reference+ }++upgradePreGuidanceTypeRule :: PreGuidanceTypeRule -> TypeRule+upgradePreGuidanceTypeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePreGuidanceFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradePreGuidanceProfile :: PreGuidanceProfileSpec -> ProfileSpec+upgradePreGuidanceProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreGuidanceFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = previous ^. #requireBundleVersion,+ types = map upgradePreGuidanceTypeRule (previous ^. #types)+ }++upgradePreNestedReferenceHandleRule :: PreNestedReferenceHandleReferenceRule -> HandleReferenceRule+upgradePreNestedReferenceHandleRule previous =+ HandleReferenceRule+ { localPrefix = previous ^. #localPrefix,+ externalUriSchemes = previous ^. #externalUriSchemes,+ allowSelf = previous ^. #allowSelf,+ allowLocal = True,+ externalUriPattern = Nothing+ }++upgradePreNestedReferenceFrontmatter :: PreNestedReferenceFrontmatterRules -> FrontmatterRules+upgradePreNestedReferenceFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = upgradeNestedRules <$> rule ^. #objectFields,+ reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,+ path = rule ^. #path,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ path = rule ^. #path,+ when = rule ^. #when,+ reference = Nothing+ }++upgradePreNestedReferenceTypeRule :: PreNestedReferenceTypeRule -> TypeRule+upgradePreNestedReferenceTypeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePreNestedReferenceFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradePreNestedReferenceProfile :: PreNestedReferenceProfileSpec -> ProfileSpec+upgradePreNestedReferenceProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreNestedReferenceFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = previous ^. #requireBundleVersion,+ types = map upgradePreNestedReferenceTypeRule (previous ^. #types)+ }++upgradePrePathProfileFrontmatter :: PrePathProfileFrontmatterRules -> FrontmatterRules+upgradePrePathProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = upgradeNestedRules <$> rule ^. #objectFields,+ reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,+ path = Nothing,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreGuidanceFieldFormat <$> rule ^. #format,+ path = Nothing,+ when = rule ^. #when,+ reference = Nothing+ }++upgradePreActorProfileFrontmatter :: PreActorProfileFrontmatterRules -> FrontmatterRules+upgradePreActorProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = upgradeNestedRules <$> rule ^. #objectFields,+ reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,+ path = Nothing,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ path = Nothing,+ when = rule ^. #when,+ reference = Nothing+ }++upgradePreObjectProfileFrontmatter :: PreObjectProfileFrontmatterRules -> FrontmatterRules+upgradePreObjectProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = map upgradeField (previous ^. #optional)+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = Nothing,+ reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,+ path = Nothing,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = map upgradeNestedField (rules ^. #optional)+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ path = Nothing,+ when = rule ^. #when,+ reference = Nothing+ }++upgradeReferenceProfileFrontmatter :: ReferenceProfileFrontmatterRules -> FrontmatterRules+upgradeReferenceProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = Nothing,+ reference = upgradePreNestedReferenceHandleRule <$> rule ^. #reference,+ path = Nothing,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = []+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ path = Nothing,+ when = rule ^. #when,+ reference = Nothing+ }++upgradePreviousFrontmatter :: PreviousFrontmatterRules -> FrontmatterRules+upgradePreviousFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = [],+ cardinality = Any,+ format = Nothing,+ elementFields = Nothing,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = Nothing,+ uniqueBy = Nothing+ }++upgradeConditionalProfileFrontmatter :: ConditionalProfileFrontmatterRules -> FrontmatterRules+upgradeConditionalProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedRules <$> rule ^. #elementFields,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = rule ^. #when,+ uniqueBy = Nothing+ }+ upgradeNestedRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = []+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ path = Nothing,+ when = rule ^. #when,+ reference = Nothing+ }++upgradeNestedProfileFrontmatter :: NestedProfileFrontmatterRules -> FrontmatterRules+upgradeNestedProfileFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = upgradeNestedProfileRules <$> rule ^. #elementFields,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = Nothing,+ uniqueBy = Nothing+ }+ upgradeNestedProfileRules rules =+ NestedRules+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeNestedField (rules ^. #required),+ recommended = map upgradeNestedField (rules ^. #recommended),+ optional = []+ }+ upgradeNestedField rule =+ NestedFieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ path = Nothing,+ when = Nothing,+ reference = Nothing+ }++upgradeFormatFrontmatter :: FormatFrontmatterRules -> FrontmatterRules+upgradeFormatFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = upgradePreV02FieldFormat <$> rule ^. #format,+ elementFields = Nothing,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = Nothing,+ uniqueBy = Nothing+ }++upgradeCardinalityFrontmatter :: CardinalityFrontmatterRules -> FrontmatterRules+upgradeCardinalityFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = rule ^. #cardinality,+ format = Nothing,+ elementFields = Nothing,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = Nothing,+ uniqueBy = Nothing+ }++upgradeVocabularyFrontmatter :: VocabularyFrontmatterRules -> FrontmatterRules+upgradeVocabularyFrontmatter previous =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map upgradeField (previous ^. #required),+ recommended = map upgradeField (previous ^. #recommended),+ optional = []+ }+ where+ upgradeField rule =+ FieldRule+ { field = rule ^. #field,+ description = rule ^. #description,+ allowedValues = rule ^. #allowedValues,+ cardinality = Any,+ format = Nothing,+ elementFields = Nothing,+ objectFields = Nothing,+ reference = Nothing,+ path = Nothing,+ when = Nothing,+ uniqueBy = Nothing+ }++-- | Lift the generation frozen before @requireBundleVersion@ forward. Its+-- contained rule records use the shared 0.7.0.0 frozen types because those+-- records later grew too.+upgradePreBundleVersionProfile :: PreBundleVersionProfileSpec -> ProfileSpec+upgradePreBundleVersionProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreNestedReferenceFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradePreNestedReferenceTypeRule (previous ^. #types)+ }++upgradePrePathProfile :: PrePathProfileSpec -> ProfileSpec+upgradePrePathProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePrePathProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePrePathProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradePreActorProfile :: PreActorProfileSpec -> ProfileSpec+upgradePreActorProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreActorProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePreActorProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradePreObjectProfile :: PreObjectProfileSpec -> ProfileSpec+upgradePreObjectProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreObjectProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePreObjectProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeReferenceProfile :: ReferenceProfileSpec -> ProfileSpec+upgradeReferenceProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeReferenceProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeReferenceProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeConditionalProfile :: ConditionalProfileSpec -> ProfileSpec+upgradeConditionalProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeConditionalProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeConditionalProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeNestedProfile :: NestedProfileSpec -> ProfileSpec+upgradeNestedProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeNestedProfileFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeNestedProfileFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeFormatProfile :: FormatProfileSpec -> ProfileSpec+upgradeFormatProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeFormatFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeFormatFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeCardinalityProfile :: CardinalityProfileSpec -> ProfileSpec+upgradeCardinalityProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeCardinalityFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeCardinalityFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeVocabularyProfile :: VocabularyProfileSpec -> ProfileSpec+upgradeVocabularyProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradeVocabularyFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = previous ^. #allowUnknownFields,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradeVocabularyFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeTypeAwareProfile :: TypeAwareProfileSpec -> ProfileSpec+upgradeTypeAwareProfile previous =+ ProfileSpec+ { name = previous ^. #name,+ description = previous ^. #description,+ guidance = Nothing,+ okfVersion = previous ^. #okfVersion,+ frontmatter = upgradePreviousFrontmatter (previous ^. #frontmatter),+ allowUnknownTypes = previous ^. #allowUnknownTypes,+ allowUnknownFields = True,+ idField = previous ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (previous ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = upgradePreviousFrontmatter (rule ^. #frontmatter),+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++upgradeDescribedProfile :: DescribedProfileSpec -> ProfileSpec+upgradeDescribedProfile described =+ ProfileSpec+ { name = described ^. #name,+ description = described ^. #description,+ guidance = Nothing,+ okfVersion = described ^. #okfVersion,+ frontmatter = upgradePreviousFrontmatter (described ^. #frontmatter),+ allowUnknownTypes = described ^. #allowUnknownTypes,+ allowUnknownFields = True,+ idField = described ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (described ^. #types)+ }+ where+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = rule ^. #description,+ guidance = Nothing,+ frontmatter = emptyFrontmatterRules,+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++-- | Lift a 0.2.x profile into the current shape by attaching neither+-- descriptions nor guidance, and by supplying empty type-specific frontmatter.+upgradeLegacyProfile :: LegacyProfileSpec -> ProfileSpec+upgradeLegacyProfile legacy =+ ProfileSpec+ { name = legacy ^. #name,+ description = Nothing,+ guidance = Nothing,+ okfVersion = legacy ^. #okfVersion,+ frontmatter =+ FrontmatterRules+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = map undocumented (legacy ^. #frontmatter . #required),+ recommended = map undocumented (legacy ^. #frontmatter . #recommended),+ optional = []+ },+ allowUnknownTypes = legacy ^. #allowUnknownTypes,+ allowUnknownFields = True,+ idField = legacy ^. #idField,+ requireBundleVersion = Nothing,+ types = map upgradeRule (legacy ^. #types)+ }+ where+ undocumented key = FieldRule {field = key, description = Nothing, allowedValues = [], cardinality = Any, format = Nothing, elementFields = Nothing, objectFields = Nothing, reference = Nothing, path = Nothing, when = Nothing, uniqueBy = Nothing}+ upgradeRule rule =+ TypeRule+ { type_ = rule ^. #type_,+ description = Nothing,+ guidance = Nothing,+ frontmatter = emptyFrontmatterRules,+ pathPattern = rule ^. #pathPattern,+ resourceScheme = rule ^. #resourceScheme,+ requireSchemaSection = rule ^. #requireSchemaSection,+ schemaColumns = rule ^. #schemaColumns,+ idPrefix = rule ^. #idPrefix+ }++-- | Load and decode a Dhall profile descriptor from a file path. Any evaluation+-- or decoding failure is captured as a human-readable 'Left'.+--+-- The pre-nested-closure, pre-product-discovery, pre-guidance,+-- pre-nested-reference, pre-bundle-version,+-- pre-path shape, pre-actor shape, pre-object+-- shape, reference-aware shape,+-- condition-aware shape, bounded-nested shape, EP-4+-- format shape, EP-3 cardinality shape, EP-2 vocabulary shape, type-aware EP-1+-- shape, self-documenting shape, and okf 0.2.x shape are accepted by frozen+-- fallback decoders and upgraded+-- with their no-op defaults. When every decoder fails, the /current/ decoder's+-- error is reported.+loadProfileFile :: FilePath -> IO (Either Text ProfileSpec)+loadProfileFile path = do+ current <- tryDecode (Dhall.inputFile auto path)+ case current of+ Right spec -> pure (Right spec)+ -- Only the current decoder's error is kept. An author wants to know how+ -- their descriptor differs from today's schema, not from a retired one.+ Left currentError -> maybe (Left currentError) Right <$> firstFrozen frozenDecoders+ where+ -- Newest generation first. Each entry looks identical but is inferred at a+ -- distinct result type, fixed by the upgrade function it names; @auto@ then+ -- picks that generation's decoder. Adding a generation is one line here.+ frozenDecoders :: [IO (Maybe ProfileSpec)]+ frozenDecoders =+ [ attempt upgradePreNestedClosureProfileSpec,+ attempt upgradePreProductDiscoveryProfile,+ attempt upgradePreGuidanceProfile,+ attempt upgradePreNestedReferenceProfile,+ attempt upgradePreBundleVersionProfile,+ attempt upgradePrePathProfile,+ attempt upgradePreActorProfile,+ attempt upgradePreObjectProfile,+ attempt upgradeReferenceProfile,+ attempt upgradeConditionalProfile,+ attempt upgradeNestedProfile,+ attempt upgradeFormatProfile,+ attempt upgradeCardinalityProfile,+ attempt upgradeVocabularyProfile,+ attempt upgradeTypeAwareProfile,+ attempt upgradeDescribedProfile,+ attempt upgradeLegacyProfile+ ]++ -- Short-circuiting, so a descriptor that decodes at the first frozen+ -- generation costs one extra parse rather than thirteen.+ firstFrozen :: [IO (Maybe ProfileSpec)] -> IO (Maybe ProfileSpec)+ firstFrozen [] = pure Nothing+ firstFrozen (decoder : remaining) =+ decoder >>= \case+ Just spec -> pure (Just spec)+ Nothing -> firstFrozen remaining++ attempt :: (FromDhall a) => (a -> ProfileSpec) -> IO (Maybe ProfileSpec)+ attempt upgrade = either (const Nothing) (Just . upgrade) <$> tryDecode (Dhall.inputFile auto path)++ tryDecode :: IO a -> IO (Either Text a)+ tryDecode action =+ (Right <$> action)+ `catchSynchronous` \(exception :: SomeException) -> pure (Left (Text.pack (show exception)))++-- | Does an already-evaluated Dhall expression decode as a profile? Tries the+-- current schema, then the pre-nested-closure, pre-product-discovery,+-- pre-guidance, pre-nested-reference,+-- pre-bundle-version, pre-path, pre-actor, pre-object,+-- reference-aware,+-- condition-aware, bounded-nested, EP-4, EP-3, EP-2, EP-1, self-documenting, and+-- okf 0.2.x schemas, so the published @okf-profiles@ package still enumerates.+-- Uses 'Dhall.rawInput', which normalizes and runs the decoder's extractor+-- without throwing, which is what lets registry enumeration be pure.+decodeProfileExpr :: Expr Src Void -> Maybe ProfileSpec+decodeProfileExpr rawExpression =+ Dhall.rawInput Dhall.auto expression+ <|> ( if containsNestedClosureField expression+ then Nothing+ else+ fmap upgradePreNestedClosureProfileSpec (Dhall.rawInput Dhall.auto expression)+ <|> ( if containsProductDiscoveryField expression+ then Nothing+ else+ fmap upgradePreProductDiscoveryProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradePreGuidanceProfile (Dhall.rawInput Dhall.auto expression)+ <|> decodeProfilesBeforeGuidance expression+ )+ )+ where+ expression = Dhall.Core.normalize rawExpression++-- | Inspect only nested-rule records reached through descriptor field rules.+-- The profile root has carried the same name since before nested closure.+containsNestedClosureField :: Expr Src Void -> Bool+containsNestedClosureField expression =+ any nestedClosure (frontmatters expression)+ where+ member key (Dhall.Core.RecordLit fields) =+ Dhall.Core.recordFieldValue <$> Dhall.Map.lookup key fields+ member _ _ = Nothing+ listValues (Dhall.Core.ListLit _ values) = toList values+ listValues _ = []+ frontmatters profile =+ maybeToList (member "frontmatter" profile)+ <> [ fm+ | typeValues <- maybeToList (member "types" profile),+ typeRule <- listValues typeValues,+ fm <- maybeToList (member "frontmatter" typeRule)+ ]+ nestedClosure fm =+ or+ [ Dhall.Map.member "allowUnknownFields" fields+ | listName <- ["required", "recommended", "optional"],+ rules <- maybeToList (member listName fm),+ rule <- listValues rules,+ shape <- ["elementFields", "objectFields"],+ Dhall.Core.Some nested <- maybeToList (member shape rule),+ Dhall.Core.RecordLit fields <- [nested]+ ]++-- | The generic Dhall record extractor deliberately ignores extra record+-- members. Without this guard, a value from the 0.8.0.0 generation can fall+-- through to an older decoder and silently lose nested-reference or uniqueness+-- declarations. Every non-empty 0.8.0.0 field-rule value carries at least one+-- of these post-0.7 record members, even when its value is @None@.+decodeProfilesBeforeGuidance :: Expr Src Void -> Maybe ProfileSpec+decodeProfilesBeforeGuidance expression+ | containsPost07DescriptorField expression = Nothing+ | otherwise =+ fmap upgradePreNestedReferenceProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradePreBundleVersionProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradePrePathProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradePreActorProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradePreObjectProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeReferenceProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeConditionalProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeNestedProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeFormatProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeCardinalityProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeVocabularyProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeTypeAwareProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeDescribedProfile (Dhall.rawInput Dhall.auto expression)+ <|> fmap upgradeLegacyProfile (Dhall.rawInput Dhall.auto expression)++containsPost07DescriptorField :: Expr Src Void -> Bool+containsPost07DescriptorField =+ anyOf (cosmosOf Dhall.Core.subExpressions) $ \case+ Dhall.Core.RecordLit fields ->+ any (`Dhall.Map.member` fields) ["uniqueBy", "allowLocal", "externalUriPattern"]+ _ -> False++-- | The description a profile attaches to a frontmatter key, looking in+-- @required@ first, then @recommended@, then @optional@. 'Nothing' when the key+-- is undocumented or absent from the profile entirely.+profileFieldDescription :: ProfileSpec -> Text -> Maybe Text+profileFieldDescription spec key =+ case [rule | rule <- rules, rule ^. #field == key] of+ (rule : _) -> rule ^. #description+ [] -> Nothing+ where+ rules =+ spec ^. #frontmatter . #required+ <> spec ^. #frontmatter . #recommended+ <> spec ^. #frontmatter . #optional++-- | A malformed profile definition. The optional type is absent for profile+-- scope and present for a type-specific scope. The list name is @required@,+-- @recommended@, or @optional@.+data ProfileDefinitionError+ = InvalidConceptConstraint (Maybe Text) (Maybe Text) ValueSelector Text+ | DuplicateTypeRule Text+ | DuplicateFieldRule (Maybe Text) Text Text+ | ConflictingFieldRequirement (Maybe Text) Text+ | UnsatisfiableVocabulary (Maybe Text) Text [Text] [Text]+ | ConflictingCardinality (Maybe Text) Text Cardinality Cardinality+ | ElementFieldsRequireList (Maybe Text) FieldPath Cardinality+ | InvalidFormatParameter FieldPath FieldFormat Text+ | ConflictingFieldFormat FieldPath FieldFormat FieldFormat+ | EmptyConditionValues (Maybe Text) FieldPath FieldPath+ | ConditionFieldNotDeclared (Maybe Text) FieldPath FieldPath+ | ConditionFieldNotScalar (Maybe Text) FieldPath FieldPath Cardinality+ | ConditionFieldOpenVocabulary (Maybe Text) FieldPath FieldPath+ | ConditionFieldHasUnreachableValues (Maybe Text) FieldPath FieldPath [Text] [Text]+ | SelfConditionalField (Maybe Text) FieldPath+ | InvalidReferencePrefix (Maybe Text) FieldPath Text+ | ReferencePrefixNotDeclared (Maybe Text) FieldPath Text+ | ReferenceRequiresIdField (Maybe Text) FieldPath+ | InvalidExternalReferenceScheme (Maybe Text) FieldPath Text+ | ConflictingReferencePrefix Text FieldPath Text Text+ | ReferenceWithFormat (Maybe Text) FieldPath FieldFormat+ | -- | an @optional@ rule carries a @when@ condition, which gates only presence+ -- and is therefore dead: an optional rule has no presence check at all+ OptionalFieldWithCondition (Maybe Text) FieldPath+ | -- | a rule declares @objectFields@ alongside an explicit scalar or list+ -- cardinality; an object is neither, so the pairing cannot be satisfied+ ObjectFieldsRequireObjectShape (Maybe Text) FieldPath Cardinality+ | -- | one rule declares both a document-handle policy and a path policy;+ -- a value cannot be resolved as both a handle and a path+ PathReferenceWithHandleReference (Maybe Text) FieldPath+ | InvalidExternalUriPattern (Maybe Text) FieldPath Text Text+ | ConflictingExternalUriPatterns Text FieldPath Text Text+ | UniqueByRequiresElementFields (Maybe Text) FieldPath Text+ | UniqueByFieldNotDeclared (Maybe Text) FieldPath+ | UniqueByFieldNotUnconditionallyRequired (Maybe Text) FieldPath+ | UniqueByFieldNotScalar (Maybe Text) FieldPath Cardinality+ | ConflictingUniqueBy Text FieldPath Text Text+ | -- | @okfVersion@ is not @\<major\>.\<minor\>@+ InvalidProfileOkfVersion Text+ | -- | @okfVersion@ names a major version okf does not implement, so okf cannot+ -- know which of its rules still hold+ ProfileOkfVersionNotUnderstood Text+ | -- | @requireBundleVersion@ is not @\<major\>.\<minor\>@, so no bundle+ -- declaration could ever be compared against it+ InvalidRequiredBundleVersion Text+ | -- | a required or recommended rule names a key the declared version+ -- supersedes (scope, path, declared version, version that superseded the key)+ FieldSupersededInOkfVersion (Maybe Text) FieldPath Text Text+ | -- | a rule names a value format introduced after the declared version+ -- (scope, path, format, declared version, version that introduced the format)+ FormatRequiresOkfVersion (Maybe Text) FieldPath FieldFormat Text Text+ deriving stock (Generic, Eq, Ord, Show)++-- | Whether a 'PresenceClause' demands a key or merely recommends it. There is+-- deliberately no @OptionalField@ constructor: an optional key is encoded as an+-- 'EffectiveFieldRule' with /no/ presence clauses at all.+data FieldRequirement = RecommendedField | RequiredField+ deriving stock (Generic, Eq, Ord, Show)++-- | One reason a key may have to be present, optionally gated by a condition on+-- another same-scope key. Abstract: read it with 'presenceClauseRequirement' and+-- 'presenceClauseCondition'.+data PresenceClause = PresenceClause+ { requirement :: !FieldRequirement,+ condition :: !(Maybe FieldCondition)+ }+ deriving stock (Generic, Eq, Show)++-- | Everything that actually applies to one frontmatter key for one concept+-- type: the profile-scope declaration merged with the type-scope one per+-- [ADR 5](docs/adr/5-compile-profile-rules-before-validation.md). Abstract: read+-- it with the @fieldRule*@ accessors below, never by pattern matching, so that+-- later profile features can extend it without breaking consumers.+data EffectiveFieldRule = EffectiveFieldRule+ { presenceClauses :: ![PresenceClause],+ description :: !(Maybe Text),+ allowedValues :: ![Text],+ cardinality :: !Cardinality,+ format :: !(Maybe FieldFormat),+ elementFields :: !(Maybe (Map Text EffectiveFieldRule)),+ objectFields :: !(Maybe (Map Text EffectiveFieldRule)),+ elementAllowsUnknownFields :: !(Maybe Bool),+ objectAllowsUnknownFields :: !(Maybe Bool),+ reference :: !(Maybe HandleReferenceRule),+ path :: !(Maybe PathReferenceRule),+ uniqueBy :: !(Maybe Text),+ elementConstraints :: !EffectiveConceptConstraints,+ objectConstraints :: !EffectiveConceptConstraints,+ compiledExternalUriPattern :: !(Maybe Regex)+ }+ deriving stock (Generic)++instance Eq EffectiveFieldRule where+ left == right =+ left ^. #elementConstraints == right ^. #elementConstraints+ && left ^. #objectConstraints == right ^. #objectConstraints+ && left ^. #presenceClauses == right ^. #presenceClauses+ && left ^. #description == right ^. #description+ && left ^. #allowedValues == right ^. #allowedValues+ && left ^. #cardinality == right ^. #cardinality+ && left ^. #format == right ^. #format+ && left ^. #elementAllowsUnknownFields == right ^. #elementAllowsUnknownFields+ && left ^. #objectAllowsUnknownFields == right ^. #objectAllowsUnknownFields+ && left ^. #elementFields == right ^. #elementFields+ && left ^. #objectFields == right ^. #objectFields+ && left ^. #reference == right ^. #reference+ && left ^. #path == right ^. #path+ && left ^. #uniqueBy == right ^. #uniqueBy++instance Show EffectiveFieldRule where+ show rule =+ "EffectiveFieldRule "+ <> show+ ( rule ^. #presenceClauses,+ rule ^. #description,+ rule ^. #allowedValues,+ rule ^. #cardinality,+ rule ^. #format,+ rule ^. #elementFields,+ rule ^. #objectFields,+ rule ^. #reference,+ rule ^. #path,+ rule ^. #uniqueBy,+ rule ^. #elementAllowsUnknownFields,+ rule ^. #objectAllowsUnknownFields+ )++-- | The stable lowercase display name for a cardinality: @any@, @scalar@,+-- @list@, or @object@. These are the names the CLI prints and the names+-- generated profile documentation uses, so a reader who has seen one recognizes+-- the other. @object@ is also the word the CLI already prints for an actual+-- mapping value, so a cardinality-mismatch message reads coherently.+renderCardinalityName :: Cardinality -> Text+renderCardinalityName = \case+ Any -> "any"+ Scalar -> "scalar"+ List -> "list"+ Object -> "object"++-- | The stable display name for a named format: @rfc3339-utc@, @date@, @uri@,+-- @uri-with-scheme(SCHEME)@, @document-handle(PREFIX)@, @actor@,+-- @human-actor@, @integer@, @non-negative-integer@, or @boolean@. As with+-- 'renderCardinalityName', this vocabulary is shared between the CLI and+-- generated documentation and must not drift between them.+renderFieldFormatName :: FieldFormat -> Text+renderFieldFormatName = \case+ Rfc3339Utc -> "rfc3339-utc"+ Date -> "date"+ Uri -> "uri"+ UriWithScheme scheme -> "uri-with-scheme(" <> scheme <> ")"+ DocumentHandle prefix -> "document-handle(" <> prefix <> ")"+ Actor -> "actor"+ HumanActor -> "human-actor"+ Integer -> "integer"+ NonNegativeInteger -> "non-negative-integer"+ FiniteDecimal -> "finite-decimal"+ Boolean -> "boolean"++-- | The presence clauses that govern whether this key must be present, in the+-- order the profile declared them. An __empty list means the key is optional__:+-- it is fully validated whenever it is present and never reported when absent,+-- in any validation mode. A clause with 'RequiredField' is always checked; a+-- clause with 'RecommendedField' is checked only under+-- 'Okf.Validation.StrictAuthoring'. A clause carrying a 'FieldCondition' applies+-- only when that condition holds for the document being checked.+fieldRulePresenceClauses :: EffectiveFieldRule -> [PresenceClause]+fieldRulePresenceClauses rule = rule ^. #presenceClauses++-- | Prose documenting the key, merged across profile and type scope with+-- type-level prose winning. Purely documentary: it is never checked against a+-- document and can never produce a 'ProfileViolation'.+fieldRuleDescription :: EffectiveFieldRule -> Maybe Text+fieldRuleDescription rule = rule ^. #description++-- | The closed vocabulary of permitted textual values. An __empty list means+-- unconstrained__, not "no value is permitted".+fieldRuleAllowedValues :: EffectiveFieldRule -> [Text]+fieldRuleAllowedValues rule = rule ^. #allowedValues++-- | Whether the key must be a single value, a non-empty list, or either.+fieldRuleCardinality :: EffectiveFieldRule -> Cardinality+fieldRuleCardinality rule = rule ^. #cardinality++-- | The named textual format constraining present values, if any.+fieldRuleFormat :: EffectiveFieldRule -> Maybe FieldFormat+fieldRuleFormat rule = rule ^. #format++-- | The document-reference policy for this key, if any.+fieldRuleReference :: EffectiveFieldRule -> Maybe HandleReferenceRule+fieldRuleReference rule = rule ^. #reference++-- | The required scalar member whose values must be unique within this one+-- list of records, or 'Nothing' when no list-local key is declared.+fieldRuleUniqueBy :: EffectiveFieldRule -> Maybe Text+fieldRuleUniqueBy rule = rule ^. #uniqueBy++-- | The path-valued policy for this key, if any. Distinct from+-- 'fieldRuleReference': a handle resolves against the bundle's document-ID+-- index, a path against its concept tree. A rule never carries both — compiling+-- one that does is a 'PathReferenceWithHandleReference' definition error — so a+-- consumer can read whichever is present without disambiguating.+--+-- Unlike 'fieldRuleReference' this can be present on a rule taken from+-- 'fieldRuleElementFields' or 'fieldRuleObjectFields', because+-- @sources[].resource@ is the field the policy exists for.+fieldRulePath :: EffectiveFieldRule -> Maybe PathReferenceRule+fieldRulePath rule = rule ^. #path++-- | Rules for the flat object stored in each element of a list-valued key,+-- keyed by nested key name, or 'Nothing' when the key declares no nested shape.+-- Nested rules are depth-bounded: a nested rule never itself has element fields,+-- so 'fieldRuleElementFields' on a value taken from this map is always+-- 'Nothing'.+fieldRuleElementFields :: EffectiveFieldRule -> Maybe (Map Text EffectiveFieldRule)+fieldRuleElementFields rule = rule ^. #elementFields++-- | Rules for the members of the mapping stored at this key, keyed by member+-- name, or 'Nothing' when the key declares no object shape. Like+-- 'fieldRuleElementFields' this is depth-bounded: a value taken from this map+-- always has 'Nothing' for both nested accessors. A rule may declare both, which+-- means either spelling of the value is accepted and both are checked against+-- the same member rules.+fieldRuleObjectFields :: EffectiveFieldRule -> Maybe (Map Text EffectiveFieldRule)+fieldRuleObjectFields rule = rule ^. #objectFields++-- | Effective per-shape closure. Nothing means no rule for that shape;+-- Just False with an empty member map forbids every supplied member name.+fieldRuleElementAllowsUnknownFields :: EffectiveFieldRule -> Maybe Bool+fieldRuleElementAllowsUnknownFields rule = rule ^. #elementAllowsUnknownFields++fieldRuleObjectAllowsUnknownFields :: EffectiveFieldRule -> Maybe Bool+fieldRuleObjectAllowsUnknownFields rule = rule ^. #objectAllowsUnknownFields++-- | Whether this clause demands the key or merely recommends it.+presenceClauseRequirement :: PresenceClause -> FieldRequirement+presenceClauseRequirement clause = clause ^. #requirement++-- | The same-scope predicate gating this clause, or 'Nothing' when it always+-- applies.+presenceClauseCondition :: PresenceClause -> Maybe FieldCondition+presenceClauseCondition clause = clause ^. #condition++-- | A raw profile whose authoring contradictions have been rejected and whose+-- effective profile-plus-type frontmatter rules have been precomputed.+data CompiledProfile = CompiledProfile+ { spec :: !ProfileSpec,+ baseRules :: !(Map Text EffectiveFieldRule),+ baseConstraints :: !EffectiveConceptConstraints,+ constraintsByType :: !(Map Text EffectiveConceptConstraints),+ rulesByType :: !(Map Text (Map Text EffectiveFieldRule)),+ -- | @requireBundleVersion@ already parsed, so 'validateProfileVersion' never+ -- re-parses and cannot disagree with what compilation accepted.+ requiredBundleVersion :: !(Maybe OkfVersion)+ }+ deriving stock (Generic, Eq, Show)++compiledProfileSpec :: CompiledProfile -> ProfileSpec+compiledProfileSpec compiled = compiled ^. #spec++-- | The minimum OKF version the profile requires its bundle to declare, already+-- parsed. 'Nothing' when the profile requires nothing, which is the default and+-- the case for almost every profile.+compiledProfileRequiredBundleVersion :: CompiledProfile -> Maybe OkfVersion+compiledProfileRequiredBundleVersion compiled = compiled ^. #requiredBundleVersion++-- | The concept @type@ strings the profile declares, in the order the descriptor+-- declares them. Declaration order is the author's and is preserved because+-- documentation and display should follow it rather than an alphabetical+-- reordering.+compiledProfileTypeNames :: CompiledProfile -> [Text]+compiledProfileTypeNames compiled =+ [rule ^. #type_ | rule <- compiledProfileSpec compiled ^. #types]++-- | The rules that apply to every document, whatever its type, keyed by+-- frontmatter key name.+compiledProfileBaseRules :: CompiledProfile -> Map Text EffectiveFieldRule+compiledProfileBaseRules compiled = compiled ^. #baseRules++-- | The rules that apply to a document of the given @type@: the profile-scope+-- rules merged with that type's own. A type the profile does not declare falls+-- back to the profile-scope rules alone, because a profile with+-- @allowUnknownTypes = True@ still applies its profile-wide expectations to a+-- document whose type it does not recognize.+compiledProfileRulesForType :: CompiledProfile -> Text -> Map Text EffectiveFieldRule+compiledProfileRulesForType = effectiveRulesForType++compileProfile :: ProfileSpec -> Either (NonEmpty ProfileDefinitionError) CompiledProfile+compileProfile rawSpec =+ case definitionErrors of+ firstError : remainingErrors -> Left (firstError :| remainingErrors)+ [] ->+ Right+ CompiledProfile+ { spec = rawSpec,+ baseConstraints = resolveConstraints baseRules (frontmatterConstraints (rawSpec ^. #frontmatter)),+ constraintsByType = Map.fromList [(r ^. #type_, resolveConstraints (mergeRules baseRules (compileRules (r ^. #frontmatter))) (mergeConstraints (frontmatterConstraints (rawSpec ^. #frontmatter)) (frontmatterConstraints (r ^. #frontmatter)))) | r <- rawSpec ^. #types],+ baseRules,+ rulesByType =+ Map.fromList+ [ (rule ^. #type_, mergeRules baseRules (compileRules (rule ^. #frontmatter)))+ | rule <- rawSpec ^. #types+ ],+ -- Safe here and only here: 'requiredBundleVersionErrors' is one of+ -- the checks 'definitionErrors' collects, so reaching this branch+ -- means the value parsed.+ requiredBundleVersion = rawSpec ^. #requireBundleVersion >>= parseOkfVersion+ }+ where+ baseRules = compileRules (rawSpec ^. #frontmatter)+ typeNames = map (^. #type_) (rawSpec ^. #types)+ definitionErrors =+ List.sortOn definitionErrorKey $+ map DuplicateTypeRule (duplicates typeNames)+ <> scopeErrors Nothing (rawSpec ^. #frontmatter)+ <> concat+ [ scopeErrors (Just (rule ^. #type_)) (rule ^. #frontmatter)+ | rule <- rawSpec ^. #types+ ]+ <> constraintErrors Nothing Nothing baseRules (frontmatterConstraints (rawSpec ^. #frontmatter))+ <> concat [constraintErrors (Just (r ^. #type_)) Nothing (mergeRules baseRules (compileRules (r ^. #frontmatter))) (mergeConstraints (frontmatterConstraints (rawSpec ^. #frontmatter)) (frontmatterConstraints (r ^. #frontmatter))) | r <- rawSpec ^. #types]+ <> vocabularyErrors+ <> cardinalityErrors+ <> nestedCardinalityErrors+ <> objectCardinalityErrors+ <> formatParameterErrors+ <> conflictingFormatErrors+ <> conditionDefinitionErrors+ <> referenceDefinitionErrors+ <> uniquenessDefinitionErrors+ <> versionErrors+ <> requiredBundleVersionErrors++ definitionErrorKey = \case+ InvalidConceptConstraint scope parent sel reason -> (2, fromMaybe "" scope, 32, fromMaybe "" parent <> Text.pack (show sel), Text.length reason)+ DuplicateTypeRule ctype -> (1 :: Int, ctype, 0 :: Int, "", 0 :: Int)+ DuplicateFieldRule scope listName key ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, listKey listName, key, 0)+ ConflictingFieldRequirement scope key ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 2, key, 0)+ UnsatisfiableVocabulary scope key _ _ ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 3, key, 0)+ ConflictingCardinality scope key _ _ ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 4, key, 0)+ ElementFieldsRequireList scope path cardinality ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 4, renderFieldPathKey path, fromEnum (cardinality == Scalar))+ InvalidFormatParameter path fieldFormat parameter ->+ (2, renderFieldPathKey path, 5, Text.pack (show fieldFormat), Text.length parameter)+ ConflictingFieldFormat path profileFormat typeFormat ->+ (2, renderFieldPathKey path, 6, Text.pack (show profileFormat), fromEnum (profileFormat == typeFormat))+ EmptyConditionValues scope target source -> conditionErrorKey scope target source 7+ ConditionFieldNotDeclared scope target source -> conditionErrorKey scope target source 8+ ConditionFieldNotScalar scope target source cardinality ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 9, renderFieldPathKey target <> ":" <> renderFieldPathKey source, fromEnum (cardinality == Scalar))+ ConditionFieldOpenVocabulary scope target source -> conditionErrorKey scope target source 10+ ConditionFieldHasUnreachableValues scope target source _ _ -> conditionErrorKey scope target source 11+ SelfConditionalField scope target ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 12, renderFieldPathKey target, 0)+ InvalidReferencePrefix scope target prefix -> referenceErrorKey scope target 13 prefix+ ReferencePrefixNotDeclared scope target prefix -> referenceErrorKey scope target 14 prefix+ ReferenceRequiresIdField scope target -> referenceErrorKey scope target 15 ""+ InvalidExternalReferenceScheme scope target scheme -> referenceErrorKey scope target 16 scheme+ ConflictingReferencePrefix ctype target profilePrefix typePrefix ->+ (1, ctype, 17, renderFieldPathKey target <> ":" <> profilePrefix, Text.length typePrefix)+ ReferenceWithFormat scope target fieldFormat -> referenceErrorKey scope target 18 (Text.pack (show fieldFormat))+ OptionalFieldWithCondition scope target ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 19, renderFieldPathKey target, 0)+ ObjectFieldsRequireObjectShape scope fieldPath cardinality ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 20, renderFieldPathKey fieldPath, fromEnum (cardinality == Scalar))+ PathReferenceWithHandleReference scope target -> referenceErrorKey scope target 21 ""+ InvalidExternalUriPattern scope target patternText detail ->+ referenceErrorKey scope target 22 (patternText <> ":" <> detail)+ ConflictingExternalUriPatterns ctype target profilePattern typePattern ->+ (1, ctype, 23, renderFieldPathKey target <> ":" <> profilePattern, Text.length typePattern)+ UniqueByRequiresElementFields scope target key -> uniqueErrorKey scope target 24 key+ UniqueByFieldNotDeclared scope target -> uniqueErrorKey scope target 25 ""+ UniqueByFieldNotUnconditionallyRequired scope target -> uniqueErrorKey scope target 26 ""+ UniqueByFieldNotScalar scope target cardinality -> uniqueErrorKey scope target 27 (Text.pack (show cardinality))+ ConflictingUniqueBy ctype target profileKey typeKey ->+ (1, ctype, 28, renderFieldPathKey target <> ":" <> profileKey, Text.length typeKey)+ -- The two version-parse errors are profile-wide rather than scoped, and+ -- rank below every scope rank: if the declared version is unreadable, every+ -- version-derived error below is downstream noise and the reader should see+ -- the cause first. Nothing constrains the first component to be+ -- non-negative.+ InvalidProfileOkfVersion rawVersion -> (-1, rawVersion, 0, "", 0)+ ProfileOkfVersionNotUnderstood rawVersion -> (-1, rawVersion, 1, "", 0)+ InvalidRequiredBundleVersion rawVersion -> (-1, rawVersion, 2, "", 0)+ FieldSupersededInOkfVersion scope path _declared supersededIn ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 30, renderFieldPathKey path <> ":" <> supersededIn, 0)+ FormatRequiresOkfVersion scope path fieldFormat _declared introducedIn ->+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, 31, renderFieldPathKey path <> ":" <> introducedIn, Text.length (Text.pack (show fieldFormat)))++ scopeKey Nothing = (0, "")+ scopeKey (Just ctype) = (1, ctype)+ listKey "required" = 0+ listKey _ = 1+ conditionErrorKey scope target source rank =+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, rank, renderFieldPathKey target <> ":" <> renderFieldPathKey source, 0)+ referenceErrorKey scope target rank detail =+ let (scopeRank, typeName) = scopeKey scope+ in (scopeRank, typeName, rank, renderFieldPathKey target <> ":" <> detail, 0)+ uniqueErrorKey = referenceErrorKey++ scopeErrors scope FrontmatterRules {required, recommended, optional} =+ [DuplicateFieldRule scope "required" key | key <- duplicates (map (^. #field) required)]+ <> [DuplicateFieldRule scope "recommended" key | key <- duplicates (map (^. #field) recommended)]+ <> [DuplicateFieldRule scope "optional" key | key <- duplicates (map (^. #field) optional)]+ <> [ ConflictingFieldRequirement scope key+ | key <- presenceListCollisions (map (^. #field) required) (map (^. #field) recommended) (map (^. #field) optional)+ ]+ <> concatMap (nestedScopeErrors scope) (required <> recommended <> optional)++ nestedScopeErrors scope parentRule =+ concatMap oneNestedRuleSet (declaredNestedRuleSets parentRule)+ where+ oneNestedRuleSet NestedRules {required, recommended, optional} =+ let qualify key = parentRule ^. #field <> "." <> key+ in [DuplicateFieldRule scope "nested required" (qualify key) | key <- duplicates (map (^. #field) required)]+ <> [DuplicateFieldRule scope "nested recommended" (qualify key) | key <- duplicates (map (^. #field) recommended)]+ <> [DuplicateFieldRule scope "nested optional" (qualify key) | key <- duplicates (map (^. #field) optional)]+ <> [ ConflictingFieldRequirement scope (qualify key)+ | key <- presenceListCollisions (map (^. #field) required) (map (^. #field) recommended) (map (^. #field) optional)+ ]++ -- One key classified by more than one presence list at a single scope. The+ -- profile cannot say both "always check for this" and "never check for+ -- this", so any pairing is a definition error rather than a precedence rule.+ presenceListCollisions requiredKeys recommendedKeys optionalKeys =+ List.nub . List.sort . concat $+ [ List.intersect requiredKeys recommendedKeys,+ List.intersect requiredKeys optionalKeys,+ List.intersect recommendedKeys optionalKeys+ ]++ duplicates = mapMaybe duplicateHead . List.group . List.sort+ duplicateHead (candidate : _ : _) = Just candidate+ duplicateHead _ = Nothing++ vocabularyErrors =+ [ UnsatisfiableVocabulary (Just (rule ^. #type_)) key profileValues typeValues+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ let profileValues = profileRule ^. #allowedValues+ typeValues = typeRule ^. #allowedValues,+ not (null profileValues),+ not (null typeValues),+ null (mergeVocabulary profileValues typeValues)+ ]+ <> [ UnsatisfiableVocabulary (Just (rule ^. #type_)) (parentKey <> "." <> nestedKey) profileValues typeValues+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,+ (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),+ let profileValues = profileNestedRule ^. #allowedValues+ typeValues = typeNestedRule ^. #allowedValues,+ not (null profileValues),+ not (null typeValues),+ null (mergeVocabulary profileValues typeValues)+ ]++ cardinalityErrors =+ [ ConflictingCardinality (Just (rule ^. #type_)) key profileCardinality typeCardinality+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ let profileCardinality = profileRule ^. #cardinality+ typeCardinality = typeRule ^. #cardinality,+ profileCardinality /= Any,+ typeCardinality /= Any,+ profileCardinality /= typeCardinality+ ]+ <> [ ConflictingCardinality (Just (rule ^. #type_)) (parentKey <> "." <> nestedKey) profileCardinality typeCardinality+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,+ (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),+ let profileCardinality = profileNestedRule ^. #cardinality+ typeCardinality = typeNestedRule ^. #cardinality,+ profileCardinality /= Any,+ typeCardinality /= Any,+ profileCardinality /= typeCardinality+ ]++ nestedCardinalityErrors =+ [ ElementFieldsRequireList scope (topLevelFieldPath (fieldRule ^. #field)) Scalar+ | (scope, rules) <- scopedFrontmatterRules,+ fieldRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ isJust (fieldRule ^. #elementFields),+ fieldRule ^. #cardinality == Scalar+ ]++ -- The mirror image: @objectFields@ says the value is a mapping, and neither+ -- an explicit @scalar@ nor an explicit @list@ can be one.+ objectCardinalityErrors =+ [ ObjectFieldsRequireObjectShape scope (topLevelFieldPath (fieldRule ^. #field)) declared+ | (scope, rules) <- scopedFrontmatterRules,+ fieldRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ isJust (fieldRule ^. #objectFields),+ let declared = fieldRule ^. #cardinality,+ declared == Scalar || declared == List+ ]++ scopedFrontmatterRules =+ (Nothing, rawSpec ^. #frontmatter)+ : [(Just (rule ^. #type_), rule ^. #frontmatter) | rule <- rawSpec ^. #types]++ -- The nested rule sets one raw rule declares: its list-element rules, its+ -- object-member rules, or both. Deduplicated because @mk.recordOrList@+ -- deliberately declares the same rules under both names, and a definition+ -- error names a path such as @verified.by@ that does not distinguish the two+ -- — so without this the same incoherence would be reported twice.+ declaredNestedRuleSets rawRule =+ List.nub (catMaybes [rawRule ^. #elementFields, rawRule ^. #objectFields])++ -- The same idea across two scopes: the nested maps a profile-scope rule and+ -- a type-scope rule both declare, paired shape with matching shape. Pairing+ -- an element map against an object map would compare rules that never meet.+ pairedNestedRuleMaps profileRule typeRule =+ List.nub+ [ (profileNested, typeNested)+ | nestedMap <- [(^. #elementFields), (^. #objectFields)],+ Just profileNested <- [nestedMap profileRule],+ Just typeNested <- [nestedMap typeRule]+ ]++ formatParameterErrors =+ [ InvalidFormatParameter (topLevelFieldPath (rule ^. #field)) fieldFormat parameter+ | rules <- (rawSpec ^. #frontmatter) : map (^. #frontmatter) (rawSpec ^. #types),+ rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ Just (fieldFormat, parameter) <- [invalidFormatParameter =<< rule ^. #format]+ ]+ <> [ InvalidFormatParameter (nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field)) fieldFormat parameter+ | rules <- (rawSpec ^. #frontmatter) : map (^. #frontmatter) (rawSpec ^. #types),+ rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ nestedRules <- declaredNestedRuleSets rule,+ nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,+ Just (fieldFormat, parameter) <- [invalidFormatParameter =<< nestedRule ^. #format]+ ]++ conflictingFormatErrors =+ [ ConflictingFieldFormat (topLevelFieldPath key) profileFormat typeFormat+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (key, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ Just profileFormat <- [profileRule ^. #format],+ Just typeFormat <- [typeRule ^. #format],+ mergeFieldFormat (Just profileFormat) (Just typeFormat) == Nothing+ ]+ <> [ ConflictingFieldFormat (nestedDefinitionPath parentKey nestedKey) profileFormat typeFormat+ | rule <- rawSpec ^. #types,+ let typeRules = compileRules (rule ^. #frontmatter),+ (parentKey, (profileRule, typeRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeRule,+ (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested),+ Just profileFormat <- [profileNestedRule ^. #format],+ Just typeFormat <- [typeNestedRule ^. #format],+ mergeFieldFormat (Just profileFormat) (Just typeFormat) == Nothing+ ]++ conditionDefinitionErrors =+ conditionErrors Nothing Nothing (rawSpec ^. #frontmatter) baseRules+ <> concat+ [ let typeRules = compileRules (rule ^. #frontmatter)+ mergedRules = mergeRules baseRules typeRules+ in conditionErrors (Just (rule ^. #type_)) Nothing (rule ^. #frontmatter) mergedRules+ | rule <- rawSpec ^. #types+ ]++ conditionErrors scope parent rules effectiveRules =+ concatMap (fieldConditionErrors scope parent effectiveRules) (rules ^. #required <> rules ^. #recommended)+ <> [ deadCondition scope parent (rawRule ^. #field)+ | rawRule <- rules ^. #optional,+ isJust (rawRule ^. #when)+ ]+ <> List.nub+ ( concat+ [ concatMap+ (fieldConditionErrors scope (Just (rawRule ^. #field)) effectiveNestedRules)+ (nestedRules ^. #required <> nestedRules ^. #recommended)+ <> [ deadCondition scope (Just (rawRule ^. #field)) (nestedRule ^. #field)+ | nestedRule <- nestedRules ^. #optional,+ isJust (nestedRule ^. #when)+ ]+ | rawRule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ let effectiveRule = Map.lookup (rawRule ^. #field) effectiveRules,+ (nestedRules, effectiveNestedRules) <-+ catMaybes+ [ (,) <$> (rawRule ^. #elementFields) <*> (effectiveRule >>= (^. #elementFields)),+ (,) <$> (rawRule ^. #objectFields) <*> (effectiveRule >>= (^. #objectFields))+ ]+ ]+ )++ -- A condition gates presence, and an optional rule has no presence clause to+ -- gate, so the pairing is dead in the descriptor rather than a weaker rule.+ deadCondition scope parent key =+ OptionalFieldWithCondition+ scope+ (maybe (topLevelFieldPath key) (\parentKey -> nestedDefinitionPath parentKey key) parent)++ fieldConditionErrors scope parent effectiveRules rawRule =+ case rawRule ^. #when of+ Nothing -> []+ Just FieldCondition {field = sourceKey, hasValue = conditionValues} ->+ let targetKey = rawRule ^. #field+ targetPath = maybe (topLevelFieldPath targetKey) (\parentKey -> nestedDefinitionPath parentKey targetKey) parent+ sourcePath = maybe (topLevelFieldPath sourceKey) (\parentKey -> nestedDefinitionPath parentKey sourceKey) parent+ normalizedValues = deduplicate conditionValues+ sourceErrors =+ case Map.lookup sourceKey effectiveRules of+ Nothing -> [ConditionFieldNotDeclared scope targetPath sourcePath]+ Just sourceRule ->+ [ ConditionFieldNotScalar scope targetPath sourcePath (sourceRule ^. #cardinality)+ | sourceRule ^. #cardinality /= Scalar+ ]+ <> [ ConditionFieldOpenVocabulary scope targetPath sourcePath+ | null (sourceRule ^. #allowedValues)+ ]+ <> let unreachable = filter (`notElem` sourceRule ^. #allowedValues) normalizedValues+ in [ ConditionFieldHasUnreachableValues scope targetPath sourcePath unreachable (sourceRule ^. #allowedValues)+ | not (null (sourceRule ^. #allowedValues)),+ not (null unreachable)+ ]+ in [EmptyConditionValues scope targetPath sourcePath | null normalizedValues]+ <> [SelfConditionalField scope targetPath | targetKey == sourceKey]+ <> sourceErrors++ referenceDefinitionErrors =+ List.nub $+ concatMap rawReferenceErrors scopedRules+ <> concatMap mergedReferenceErrors (rawSpec ^. #types)+ where+ declaredPrefixes = mapMaybe (^. #idPrefix) (rawSpec ^. #types)+ scopedRules =+ (Nothing, rawSpec ^. #frontmatter)+ : [(Just (rule ^. #type_), rule ^. #frontmatter) | rule <- rawSpec ^. #types]++ rawReferenceErrors (scope, rules) =+ concatMap topLevelPolicyErrors topLevelRules+ <> [ nestedError+ | rule <- topLevelRules,+ nestedRules <- declaredNestedRuleSets rule,+ nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,+ nestedError <- policyErrors scope (nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field)) (nestedRule ^. #format) (nestedRule ^. #reference) (nestedRule ^. #path)+ ]+ where+ topLevelRules = rules ^. #required <> rules ^. #recommended <> rules ^. #optional+ topLevelPolicyErrors rule =+ policyErrors scope (topLevelFieldPath (rule ^. #field)) (rule ^. #format) (rule ^. #reference) (rule ^. #path)++ policyErrors scope path declaredFormat handlePolicy pathPolicy =+ referencePolicyErrors scope path declaredFormat handlePolicy+ <> pathPolicyErrors scope path declaredFormat handlePolicy pathPolicy++ referencePolicyErrors scope path declaredFormat = \case+ Nothing -> []+ Just policy ->+ let prefix = policy ^. #localPrefix+ schemes = deduplicateSchemes (policy ^. #externalUriSchemes)+ in [InvalidReferencePrefix scope path prefix | not (validDocumentHandlePrefix prefix)]+ <> [ReferencePrefixNotDeclared scope path prefix | prefix `notElem` declaredPrefixes]+ <> [ReferenceRequiresIdField scope path | isNothing (rawSpec ^. #idField)]+ <> [InvalidExternalReferenceScheme scope path scheme | scheme <- schemes, not (validUriScheme scheme)]+ <> [ReferenceWithFormat scope path fieldFormat | Just fieldFormat <- [declaredFormat]]+ <> [ InvalidExternalUriPattern scope path patternText detail+ | Just patternText <- [policy ^. #externalUriPattern],+ Left detail <- [compileExternalUriPattern patternText]+ ]++ -- The three ways a path policy can be incoherent on its own. Two reuse+ -- the handle-reference constructors because the claim is identical: a+ -- scheme that is not a legal URI scheme, and a structural interpretation+ -- of a value paired with a textual format that would be checked against+ -- the same text. The third is genuinely new — a value cannot be resolved+ -- as both a @PREFIX-N@ handle and a §6.2 path — and cannot arise at+ -- nested scope, where 'NestedFieldRule' carries no handle policy.+ pathPolicyErrors scope path declaredFormat handlePolicy = \case+ Nothing -> []+ Just policy ->+ [ InvalidExternalReferenceScheme scope path scheme+ | scheme <- deduplicateSchemes (policy ^. #externalUriSchemes),+ not (validUriScheme scheme)+ ]+ <> [ReferenceWithFormat scope path fieldFormat | Just fieldFormat <- [declaredFormat]]+ <> [PathReferenceWithHandleReference scope path | isJust handlePolicy]++ mergedReferenceErrors typeRule =+ [ ConflictingReferencePrefix (typeRule ^. #type_) path (profilePolicy ^. #localPrefix) (typePolicy ^. #localPrefix)+ | (path, profileRule, typeFieldRule) <- pairedRules typeRule,+ Just profilePolicy <- [profileRule ^. #reference],+ Just typePolicy <- [typeFieldRule ^. #reference],+ profilePolicy ^. #localPrefix /= typePolicy ^. #localPrefix+ ]+ <> [ ConflictingExternalUriPatterns (typeRule ^. #type_) path profilePattern typePattern+ | (path, profileRule, typeFieldRule) <- pairedRules typeRule,+ Just profilePattern <- [profileRule ^. #reference >>= (^. #externalUriPattern)],+ Just typePattern <- [typeFieldRule ^. #reference >>= (^. #externalUriPattern)],+ profilePattern /= typePattern+ ]+ <> [ ReferenceWithFormat (Just (typeRule ^. #type_)) path fieldFormat+ | (path, profileRule, typeFieldRule) <- pairedRules typeRule,+ (referenceRule, formatRule) <- [(profileRule, typeFieldRule), (typeFieldRule, profileRule)],+ isJust (referenceRule ^. #reference),+ Just fieldFormat <- [formatRule ^. #format]+ ]+ <> [ PathReferenceWithHandleReference (Just (typeRule ^. #type_)) path+ | (path, profileRule, typeFieldRule) <- pairedRules typeRule,+ (referenceRule, pathRule) <- [(profileRule, typeFieldRule), (typeFieldRule, profileRule)],+ isJust (referenceRule ^. #reference),+ isJust (pathRule ^. #path)+ ]++ pairedRules typeRule =+ [ (topLevelFieldPath key, profileRule, typeFieldRule)+ | let typeFields = compileRules (typeRule ^. #frontmatter),+ (key, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeFields)+ ]+ <> [ (nestedDefinitionPath parentKey nestedKey, profileNestedRule, typeNestedRule)+ | let typeFields = compileRules (typeRule ^. #frontmatter),+ (parentKey, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeFields),+ (profileNested, typeNested) <- pairedNestedRuleMaps profileRule typeFieldRule,+ (nestedKey, (profileNestedRule, typeNestedRule)) <- Map.toAscList (Map.intersectionWith (,) profileNested typeNested)+ ]++ uniquenessDefinitionErrors =+ concatMap (validateDeclarations Nothing baseRules) [rawSpec ^. #frontmatter]+ <> concat+ [ let typeRules = compileRules (typeRule ^. #frontmatter)+ in validateDeclarations (Just (typeRule ^. #type_)) (mergeRules baseRules typeRules) (typeRule ^. #frontmatter)+ | typeRule <- rawSpec ^. #types+ ]+ <> [ ConflictingUniqueBy (typeRule ^. #type_) (topLevelFieldPath key) profileKey typeKey+ | typeRule <- rawSpec ^. #types,+ let typeRules = compileRules (typeRule ^. #frontmatter),+ (key, (profileRule, typeFieldRule)) <- Map.toAscList (Map.intersectionWith (,) baseRules typeRules),+ Just profileKey <- [profileRule ^. #uniqueBy],+ Just typeKey <- [typeFieldRule ^. #uniqueBy],+ profileKey /= typeKey+ ]+ where+ validateDeclarations scope effectiveRules rules =+ concatMap (validateDeclaration scope effectiveRules) (rules ^. #required <> rules ^. #recommended <> rules ^. #optional)++ validateDeclaration scope effectiveRules rawRule =+ case rawRule ^. #uniqueBy of+ Nothing -> []+ Just requestedKey ->+ let parentPath = topLevelFieldPath (rawRule ^. #field)+ memberPath = nestedDefinitionPath (rawRule ^. #field) requestedKey+ in case Map.lookup (rawRule ^. #field) effectiveRules >>= (^. #elementFields) of+ Nothing -> [UniqueByRequiresElementFields scope parentPath requestedKey]+ Just memberRules ->+ case Map.lookup requestedKey memberRules of+ Nothing -> [UniqueByFieldNotDeclared scope memberPath]+ Just memberRule ->+ [ UniqueByFieldNotUnconditionallyRequired scope memberPath+ | not (any unconditionalRequired (memberRule ^. #presenceClauses))+ ]+ <> [ UniqueByFieldNotScalar scope memberPath (memberRule ^. #cardinality)+ | memberRule ^. #cardinality /= Scalar+ ]++ unconditionalRequired clause =+ clause ^. #requirement == RequiredField && isNothing (clause ^. #condition)++ -- Only a value okf cannot parse is rejected. An unknown /major/ is+ -- deliberately accepted, unlike in @okfVersion@: there the profile is asking+ -- okf to interpret rules it may not understand, while here it is stating a+ -- minimum that a bundle's own declaration is compared against, which stays+ -- meaningful whatever the major is.+ requiredBundleVersionErrors =+ case rawSpec ^. #requireBundleVersion of+ Just rawVersion+ | isNothing (parseOkfVersion rawVersion) -> [InvalidRequiredBundleVersion rawVersion]+ _ -> []++ versionErrors =+ case effectiveProfileVersion (rawSpec ^. #okfVersion) of+ Left versionError -> [versionError]+ Right effectiveVersion ->+ let atLeastV02 = effectiveVersion >= okfVersion02+ v02Text = renderOkfVersion okfVersion02+ -- The /effective/ version, not the raw declared string: a profile+ -- declaring 0.9 is checked as 0.2 and its diagnostics should say so+ -- rather than repeating a number okf did not act on.+ declaredText = renderOkfVersion effectiveVersion+ in -- A key v0.2 superseded, demanded by a profile declaring v0.2 or+ -- later. Deliberately /not/ checked in the optional list: a team+ -- migrating a corpus wants @generated@ required and @timestamp@+ -- tolerated but not demanded, and the optional list says exactly+ -- that. This is the one check whose answer depends on which presence+ -- list a rule came from, so it reads the raw lists rather than the+ -- compiled map, which erases the distinction into presence clauses.+ [ FieldSupersededInOkfVersion scope (topLevelFieldPath (rule ^. #field)) declaredText v02Text+ | atLeastV02,+ (scope, rules) <- scopedFrontmatterRules,+ rule <- rules ^. #required <> rules ^. #recommended,+ rule ^. #field `elem` fieldsSupersededInV02+ ]+ -- The actor formats encode the specification §7 convention that+ -- v0.2 introduced. A format is an okf descriptor feature rather+ -- than a key name, so unlike the mirror case below it has no+ -- house-convention reading: @FieldFormat.Actor@ /is/ §7.+ <> [ FormatRequiresOkfVersion scope path fieldFormat declaredText v02Text+ | not atLeastV02,+ (scope, path, fieldFormat) <- scopedFormats,+ fieldFormat `elem` [Actor, HumanActor]+ ]++ -- There is deliberately no mirror check, "a profile declaring v0.1 names a+ -- key v0.2 introduced", which is why 'fieldsIntroducedInV02' is unused here.+ -- A profile key /name/ does not imply the OKF core key of that name: per+ -- docs/adr/1-profile-declared-document-ids.md, constraining keys the core+ -- format does not own is what profiles are for, and @status@, @sources@, and+ -- @verified@ are ordinary words teams were already using as house conventions+ -- before v0.2 claimed them. Such a check is both retroactive and ambiguous,+ -- which docs/adr/11-growing-the-profile-descriptor-language.md forbids.++ -- Every declared format paired with the path it sits at, top-level first and+ -- then nested. Only the format checks descend: 'fieldsIntroducedInV02' and+ -- 'fieldsSupersededInV02' name /concept-level/ frontmatter keys, so matching+ -- them against a member of a record would reject a profile whose element+ -- happens to be called @status@ for having a v0.2 key it does not have.+ scopedFormats =+ [ (scope, topLevelFieldPath (rule ^. #field), fieldFormat)+ | (scope, rules) <- scopedFrontmatterRules,+ rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ Just fieldFormat <- [rule ^. #format]+ ]+ <> List.nub+ [ (scope, nestedDefinitionPath (rule ^. #field) (nestedRule ^. #field), fieldFormat)+ | (scope, rules) <- scopedFrontmatterRules,+ rule <- rules ^. #required <> rules ^. #recommended <> rules ^. #optional,+ nestedRules <- declaredNestedRuleSets rule,+ nestedRule <- nestedRules ^. #required <> nestedRules ^. #recommended <> nestedRules ^. #optional,+ Just fieldFormat <- [nestedRule ^. #format]+ ]++ renderFieldPathKey (FieldPath (firstSegment :| remainingSegments)) =+ Text.intercalate "." (map renderSegment (firstSegment : remainingSegments))+ renderSegment (FieldName name) = name+ renderSegment (ArrayIndex elementIndex) = Text.pack (show elementIndex)++-- | The OKF version that introduced the v0.2 frontmatter families and the actor+-- convention. Written out rather than reusing 'supportedOkfVersion', which+-- happens to equal it today: one is "what okf implements" and the other is "what+-- introduced these keys", and they will diverge at the next minor bump.+okfVersion02 :: OkfVersion+okfVersion02 = OkfVersion {okfVersionMajor = 0, okfVersionMinor = 2}++-- | The OKF version a profile's rules are checked against, or why okf cannot+-- tell.+--+-- A higher /minor/ within a known major is clamped, mirroring+-- 'Okf.Validation.versionGate': specification §12 defines a minor bump as+-- backward-compatible additions, so every rule a v0.3 profile can express is one+-- okf already understands.+--+-- An unknown /major/ is deliberately an error, where the bundle side reads+-- best-effort — this is a considered divergence, not an oversight. §12's+-- best-effort instruction is about bundles, which may come from a third party+-- okf cannot ask. A profile is not a document okf is asked to read; it is an+-- instruction to okf about what to check, written by an author who is present.+-- Silently ignoring an instruction okf cannot interpret is worse than saying so.+-- See @docs\/adr\/10-okf-version-declaration-and-best-effort-reading.md@.+effectiveProfileVersion :: Text -> Either ProfileDefinitionError OkfVersion+effectiveProfileVersion rawVersion =+ case parseOkfVersion rawVersion of+ Nothing -> Left (InvalidProfileOkfVersion rawVersion)+ Just declared+ | okfVersionMajor declared == okfVersionMajor supportedOkfVersion ->+ Right (min declared supportedOkfVersion)+ | otherwise -> Left (ProfileOkfVersionNotUnderstood rawVersion)++compileRules :: FrontmatterRules -> Map Text EffectiveFieldRule+compileRules FrontmatterRules {required, recommended, optional} =+ Map.fromList+ ( [ (rule ^. #field, compileFieldRule RequiredField rule)+ | rule <- required+ ]+ <> [ (rule ^. #field, compileFieldRule RecommendedField rule)+ | rule <- recommended+ ]+ <> [ (rule ^. #field, compileOptionalFieldRule rule)+ | rule <- optional+ ]+ )++-- | Compile a rule the profile documents but never demands. It carries no+-- presence clause, so 'applicablePresenceClause' can never find one to report in+-- either validation mode, while every value check still runs from the+-- present-value branch. This is also the value constraints a required or+-- recommended rule is built from.+compileOptionalFieldRule :: FieldRule -> EffectiveFieldRule+compileOptionalFieldRule rule =+ normalizeRecordShapes $+ EffectiveFieldRule+ { presenceClauses = [],+ description = rule ^. #description,+ allowedValues = deduplicate (rule ^. #allowedValues),+ cardinality =+ -- Declaring a nested shape and no explicit cardinality refines what the+ -- value may be, because the shape only makes sense against one. A rule+ -- declaring both shapes stays 'Any', which is what lets either spelling+ -- of the OKF v0.2 @verified@ key satisfy it.+ case (rule ^. #objectFields, rule ^. #elementFields, rule ^. #cardinality) of+ (Just _, Just _, Any) -> Any+ (Just _, Nothing, Any) -> Object+ (Nothing, Just _, Any) -> List+ (_, _, Any) -> refineCardinalityForFormat (rule ^. #format)+ (_, _, declared) -> declared,+ format = rule ^. #format,+ elementConstraints = maybe emptyConstraints (\n -> resolveConstraints (compileNestedRules n) (nestedConstraints n)) (rule ^. #elementFields),+ objectConstraints = maybe emptyConstraints (\n -> resolveConstraints (compileNestedRules n) (nestedConstraints n)) (rule ^. #objectFields),+ elementAllowsUnknownFields = (^. #allowUnknownFields) <$> rule ^. #elementFields,+ objectAllowsUnknownFields = (^. #allowUnknownFields) <$> rule ^. #objectFields,+ elementFields = compileNestedRules <$> rule ^. #elementFields,+ objectFields = compileNestedRules <$> rule ^. #objectFields,+ reference = compiledReference,+ path = compilePathRule <$> rule ^. #path,+ uniqueBy = rule ^. #uniqueBy,+ compiledExternalUriPattern = compileReferenceMatcher compiledReference+ }+ where+ compiledReference = compileReferenceRule <$> rule ^. #reference++-- | The cardinality a rule with no declared one takes from its format.+--+-- A non-textual format implies a scalar, and without this the rule would be+-- useless: the 'Any' cardinality routes presence through 'legacyValueIsPresent',+-- which counts only non-empty text and non-empty arrays, so a @usage_count:+-- 5000@ is reported /missing/ before its value is ever examined. Refining here+-- rather than widening 'legacyValueIsPresent' keeps the meaning of every+-- descriptor that already exists — in particular a key whose value is @false@+-- keeps being reported as missing when no rule says otherwise.+--+-- An explicitly declared cardinality always wins, including @list@: a list of+-- integers is a coherent thing to demand, so pairing a numeric format with+-- 'List' is left alone rather than made an error.+refineCardinalityForFormat :: Maybe FieldFormat -> Cardinality+refineCardinalityForFormat = \case+ Just Integer -> Scalar+ Just NonNegativeInteger -> Scalar+ Just FiniteDecimal -> Scalar+ Just Boolean -> Scalar+ _ -> Any++compileFieldRule :: FieldRequirement -> FieldRule -> EffectiveFieldRule+compileFieldRule requirement rule =+ (compileOptionalFieldRule rule)+ { presenceClauses = [PresenceClause requirement (compileCondition <$> rule ^. #when)]+ }++compileNestedRules :: NestedRules -> Map Text EffectiveFieldRule+compileNestedRules NestedRules {required, recommended, optional} =+ Map.fromList+ ( [ (rule ^. #field, compileNestedFieldRule RequiredField rule)+ | rule <- required+ ]+ <> [ (rule ^. #field, compileNestedFieldRule RecommendedField rule)+ | rule <- recommended+ ]+ <> [ (rule ^. #field, compileOptionalNestedFieldRule rule)+ | rule <- optional+ ]+ )++-- | The nested counterpart of 'compileOptionalFieldRule'.+compileOptionalNestedFieldRule :: NestedFieldRule -> EffectiveFieldRule+compileOptionalNestedFieldRule rule =+ EffectiveFieldRule+ { presenceClauses = [],+ description = rule ^. #description,+ allowedValues = deduplicate (rule ^. #allowedValues),+ cardinality =+ case rule ^. #cardinality of+ Any -> refineCardinalityForFormat (rule ^. #format)+ declared -> declared,+ format = rule ^. #format,+ elementConstraints = emptyConstraints,+ objectConstraints = emptyConstraints,+ elementAllowsUnknownFields = Nothing,+ objectAllowsUnknownFields = Nothing,+ elementFields = Nothing,+ -- Nested rules stay depth-bounded: 'NestedFieldRule' has no object member,+ -- so a profile cannot constrain @sources[0].usage_window.from@.+ objectFields = Nothing,+ reference = compiledReference,+ -- But it does carry a path policy, which is the point of the member —+ -- @sources[].resource@ is only reachable here.+ path = compilePathRule <$> rule ^. #path,+ uniqueBy = Nothing,+ compiledExternalUriPattern = compileReferenceMatcher compiledReference+ }+ where+ compiledReference = compileReferenceRule <$> rule ^. #reference++compileNestedFieldRule :: FieldRequirement -> NestedFieldRule -> EffectiveFieldRule+compileNestedFieldRule requirement rule =+ (compileOptionalNestedFieldRule rule)+ { presenceClauses = [PresenceClause requirement (compileCondition <$> rule ^. #when)]+ }++mergeRules :: Map Text EffectiveFieldRule -> Map Text EffectiveFieldRule -> Map Text EffectiveFieldRule+mergeRules = Map.unionWith mergeEffectiveFieldRule++mergeEffectiveFieldRule :: EffectiveFieldRule -> EffectiveFieldRule -> EffectiveFieldRule+mergeEffectiveFieldRule profileRule typeRule =+ normalizeRecordShapes $+ EffectiveFieldRule+ { presenceClauses = profileRule ^. #presenceClauses <> typeRule ^. #presenceClauses,+ description = typeRule ^. #description <|> profileRule ^. #description,+ allowedValues = mergeVocabulary (profileRule ^. #allowedValues) (typeRule ^. #allowedValues),+ cardinality = mergeCardinality (profileRule ^. #cardinality) (typeRule ^. #cardinality),+ format = fromMaybe (profileRule ^. #format) (mergeFieldFormat (profileRule ^. #format) (typeRule ^. #format)),+ elementConstraints = resolveConstraints (fromMaybe Map.empty (mergeNestedRuleMaps (profileRule ^. #elementFields) (typeRule ^. #elementFields))) (mergeConstraints (profileRule ^. #elementConstraints) (typeRule ^. #elementConstraints)),+ objectConstraints = resolveConstraints (fromMaybe Map.empty (mergeNestedRuleMaps (profileRule ^. #objectFields) (typeRule ^. #objectFields))) (mergeConstraints (profileRule ^. #objectConstraints) (typeRule ^. #objectConstraints)),+ elementAllowsUnknownFields = mergeClosure (profileRule ^. #elementAllowsUnknownFields) (typeRule ^. #elementAllowsUnknownFields),+ objectAllowsUnknownFields = mergeClosure (profileRule ^. #objectAllowsUnknownFields) (typeRule ^. #objectAllowsUnknownFields),+ elementFields = mergeNestedRuleMaps (profileRule ^. #elementFields) (typeRule ^. #elementFields),+ objectFields = mergeNestedRuleMaps (profileRule ^. #objectFields) (typeRule ^. #objectFields),+ reference = mergedReference,+ path = mergePathRule (profileRule ^. #path) (typeRule ^. #path),+ uniqueBy = fromMaybe (profileRule ^. #uniqueBy) (mergeUniqueBy (profileRule ^. #uniqueBy) (typeRule ^. #uniqueBy)),+ compiledExternalUriPattern = compileReferenceMatcher mergedReference+ }+ where+ mergedReference = fromMaybe (profileRule ^. #reference) (mergeReferenceRule (profileRule ^. #reference) (typeRule ^. #reference))++-- | Normalize a declared condition for storage in a 'PresenceClause': the shape+-- is unchanged, but the accepted-value list is deduplicated so that a clause+-- reported in a 'ProfileViolation' does not repeat a value the author wrote+-- twice.+compileCondition :: FieldCondition -> FieldCondition+compileCondition rawCondition =+ FieldCondition+ { field = rawCondition ^. #field,+ hasValue = deduplicate (rawCondition ^. #hasValue)+ }++compileReferenceRule :: HandleReferenceRule -> HandleReferenceRule+compileReferenceRule policy =+ HandleReferenceRule+ { localPrefix = policy ^. #localPrefix,+ externalUriSchemes = map Text.toCaseFold (deduplicateSchemes (policy ^. #externalUriSchemes)),+ allowSelf = policy ^. #allowSelf,+ allowLocal = policy ^. #allowLocal,+ externalUriPattern = policy ^. #externalUriPattern+ }++compileExternalUriPattern :: Text -> Either Text Regex+compileExternalUriPattern patternText =+ first Text.pack (Regex.Text.compile defaultCompOpt defaultExecOpt patternText)++compileReferenceMatcher :: Maybe HandleReferenceRule -> Maybe Regex+compileReferenceMatcher policy = do+ patternText <- policy >>= (^. #externalUriPattern)+ either (const Nothing) Just (compileExternalUriPattern patternText)++matchesWholeExternalUriPattern :: Regex -> Text -> Bool+matchesWholeExternalUriPattern regex value =+ case Regex.Text.regexec regex value of+ Right (Just (before, _matched, after, _groups)) -> Text.null before && Text.null after+ _ -> False++compilePathRule :: PathReferenceRule -> PathReferenceRule+compilePathRule policy =+ PathReferenceRule+ { externalUriSchemes = map Text.toCaseFold (deduplicateSchemes (policy ^. #externalUriSchemes)),+ allowSelf = policy ^. #allowSelf+ }++-- | Combine a profile-scope path policy with a type-scope one: intersect the+-- permitted schemes and require both scopes to allow self-reference.+--+-- Unlike 'mergeReferenceRule' this is total and needs no @Maybe@-of-@Maybe@+-- result, because a path policy has no @localPrefix@ — the one thing two+-- handle policies can flatly disagree about. Narrowing to the intersection is+-- the same direction 'mergeVocabulary' takes: a type rule tightens the+-- profile-wide rule and never loosens it.+mergePathRule :: Maybe PathReferenceRule -> Maybe PathReferenceRule -> Maybe PathReferenceRule+mergePathRule Nothing typePolicy = typePolicy+mergePathRule profilePolicy Nothing = profilePolicy+mergePathRule (Just profilePolicy) (Just typePolicy) =+ Just+ PathReferenceRule+ { externalUriSchemes =+ filter+ (`Set.member` Set.fromList (typePolicy ^. #externalUriSchemes))+ (profilePolicy ^. #externalUriSchemes),+ allowSelf = profilePolicy ^. #allowSelf && typePolicy ^. #allowSelf+ }++-- | Either scope may close a shape; an absent declaration is the identity.+mergeClosure :: Maybe Bool -> Maybe Bool -> Maybe Bool+mergeClosure Nothing policy = policy+mergeClosure policy Nothing = policy+mergeClosure (Just left) (Just right) = Just (left && right)++-- | Merge the member maps for a shape across profile and type scopes.+mergeNestedRuleMaps :: Maybe (Map Text EffectiveFieldRule) -> Maybe (Map Text EffectiveFieldRule) -> Maybe (Map Text EffectiveFieldRule)+mergeNestedRuleMaps Nothing typeRules = typeRules+mergeNestedRuleMaps profileRules Nothing = profileRules+mergeNestedRuleMaps (Just profileRules) (Just typeRules) = Just (Map.unionWith mergeEffectiveFieldRule profileRules typeRules)++mergeReferenceRule :: Maybe HandleReferenceRule -> Maybe HandleReferenceRule -> Maybe (Maybe HandleReferenceRule)+mergeReferenceRule Nothing typePolicy = Just typePolicy+mergeReferenceRule profilePolicy Nothing = Just profilePolicy+mergeReferenceRule (Just profilePolicy) (Just typePolicy)+ | profilePolicy ^. #localPrefix == typePolicy ^. #localPrefix,+ Just mergedPattern <- mergeOptionalText (profilePolicy ^. #externalUriPattern) (typePolicy ^. #externalUriPattern) =+ Just . Just $+ HandleReferenceRule+ { localPrefix = profilePolicy ^. #localPrefix,+ externalUriSchemes =+ filter+ (`Set.member` Set.fromList (typePolicy ^. #externalUriSchemes))+ (profilePolicy ^. #externalUriSchemes),+ allowSelf = profilePolicy ^. #allowSelf && typePolicy ^. #allowSelf,+ allowLocal = profilePolicy ^. #allowLocal && typePolicy ^. #allowLocal,+ externalUriPattern = mergedPattern+ }+ | otherwise = Nothing++mergeUniqueBy :: Maybe Text -> Maybe Text -> Maybe (Maybe Text)+mergeUniqueBy = mergeOptionalText++mergeOptionalText :: Maybe Text -> Maybe Text -> Maybe (Maybe Text)+mergeOptionalText Nothing typeValue = Just typeValue+mergeOptionalText profileValue Nothing = Just profileValue+mergeOptionalText profileValue typeValue+ | profileValue == typeValue = Just profileValue+ | otherwise = Nothing++mergeFieldFormat :: Maybe FieldFormat -> Maybe FieldFormat -> Maybe (Maybe FieldFormat)+mergeFieldFormat Nothing typeFormat = Just typeFormat+mergeFieldFormat profileFormat Nothing = Just profileFormat+mergeFieldFormat (Just Uri) (Just typeFormat@(UriWithScheme _)) = Just (Just typeFormat)+mergeFieldFormat (Just profileFormat@(UriWithScheme _)) (Just Uri) = Just (Just profileFormat)+mergeFieldFormat (Just Actor) (Just HumanActor) = Just (Just HumanActor)+mergeFieldFormat (Just HumanActor) (Just Actor) = Just (Just HumanActor)+mergeFieldFormat (Just Integer) (Just NonNegativeInteger) = Just (Just NonNegativeInteger)+mergeFieldFormat (Just NonNegativeInteger) (Just Integer) = Just (Just NonNegativeInteger)+mergeFieldFormat profileFormat typeFormat+ | profileFormat == typeFormat = Just profileFormat+ | otherwise = Nothing++invalidFormatParameter :: FieldFormat -> Maybe (FieldFormat, Text)+invalidFormatParameter fieldFormat =+ case fieldFormat of+ UriWithScheme scheme+ | not (validUriScheme scheme) -> Just (fieldFormat, scheme)+ DocumentHandle prefix+ | not (validDocumentHandlePrefix prefix) -> Just (fieldFormat, prefix)+ _ -> Nothing++validUriScheme :: Text -> Bool+validUriScheme scheme =+ case Text.uncons scheme of+ Just (firstCharacter, rest) -> isAsciiLetter firstCharacter && Text.all validRest rest+ Nothing -> False+ where+ validRest character =+ isAsciiLetter character+ || ('0' <= character && character <= '9')+ || character `elem` ['+', '.', '-']++validDocumentHandlePrefix :: Text -> Bool+validDocumentHandlePrefix prefix =+ case parseDocumentId (prefix <> "-1") of+ Just documentId -> documentId ^. #prefix == prefix+ Nothing -> False++isAsciiLetter :: Char -> Bool+isAsciiLetter character = isAsciiLower character || isAsciiUpper character++mergeCardinality :: Cardinality -> Cardinality -> Cardinality+mergeCardinality Any typeCardinality = typeCardinality+mergeCardinality profileCardinality Any = profileCardinality+mergeCardinality profileCardinality _typeCardinality = profileCardinality++mergeVocabulary :: [Text] -> [Text] -> [Text]+mergeVocabulary [] typeValues = typeValues+mergeVocabulary profileValues [] = profileValues+mergeVocabulary profileValues typeValues =+ filter (`Set.member` Set.fromList typeValues) profileValues++deduplicate :: [Text] -> [Text]+deduplicate = go Set.empty+ where+ go _ [] = []+ go seen (value : rest)+ | value `Set.member` seen = go seen rest+ | otherwise = value : go (Set.insert value seen) rest++deduplicateSchemes :: [Text] -> [Text]+deduplicateSchemes = go Set.empty+ where+ go _ [] = []+ go seen (scheme : rest)+ | normalized `Set.member` seen = go seen rest+ | otherwise = scheme : go (Set.insert normalized seen) rest+ where+ normalized = Text.toCaseFold scheme++effectiveRulesForType :: CompiledProfile -> Text -> Map Text EffectiveFieldRule+effectiveRulesForType compiled ctype =+ Map.findWithDefault (compiled ^. #baseRules) ctype (compiled ^. #rulesByType)++profileFieldDescriptionForType :: CompiledProfile -> Text -> Text -> Maybe Text+profileFieldDescriptionForType compiled ctype key =+ Map.lookup key (effectiveRulesForType compiled ctype) >>= (^. #description)++-- | A parsed document handle: an ASCII-letter-led alphanumeric prefix and a+-- positive number, rendered as @PREFIX-N@.+data DocumentId = DocumentId+ { prefix :: !Text,+ number :: !Natural+ }+ deriving stock (Generic, Eq, Ord, Show)++-- | Parse a strict document handle. The prefix contains one or more ASCII+-- letters or digits and begins with a letter. It is followed by exactly one+-- hyphen and a positive decimal number with no leading zero. Thus @ADR-7@+-- parses, while @ADR-007@, @ADR-0@, @ADR-@, @-7@, @adr 7@, and+-- @ADR-7-extra@ do not.+parseDocumentId :: Text -> Maybe DocumentId+parseDocumentId raw =+ case Text.splitOn "-" raw of+ [prefixText, numberText]+ | validPrefix prefixText,+ validNumberText numberText ->+ case Text.Read.decimal numberText of+ Right (parsedNumber, remainder)+ | Text.null remainder,+ parsedNumber > 0 ->+ Just (DocumentId prefixText parsedNumber)+ _ -> Nothing+ _ -> Nothing+ where+ validPrefix value =+ case Text.uncons value of+ Just (firstCharacter, rest) ->+ isAsciiLetter firstCharacter && Text.all isAsciiAlphaNumeric rest+ Nothing -> False+ validNumberText value =+ case Text.uncons value of+ Just (firstCharacter, rest) ->+ firstCharacter >= '1'+ && firstCharacter <= '9'+ && Text.all isAsciiDigit rest+ Nothing -> False+ isAsciiDigit character =+ character >= '0' && character <= '9'+ isAsciiAlphaNumeric character =+ isAsciiLetter character || isAsciiDigit character++-- | Render a document handle as @PREFIX-N@.+renderDocumentId :: DocumentId -> Text+renderDocumentId DocumentId {prefix, number} =+ prefix <> "-" <> Text.pack (show number)++-- | Every well-formed handle under the profile's ID field, paired with the+-- concept carrying it and sorted by prefix, number, then concept ID. Concepts+-- without a well-formed handle are omitted. A profile with no ID field yields+-- an empty list.+documentIdsInBundle :: ProfileSpec -> [Concept] -> [(DocumentId, ConceptId)]+documentIdsInBundle spec concepts =+ case spec ^. #idField of+ Nothing -> []+ Just fieldName ->+ List.sortOn+ (\(documentId, cid) -> (documentId, renderConceptId cid))+ [ (documentId, conceptIdOf concept)+ | concept <- concepts,+ Just (String rawDocumentId) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],+ Just documentId <- [parseDocumentId rawDocumentId]+ ]++-- | Allocate one more than the highest document-ID number already used for the+-- given prefix, or number 1 when the prefix is unused. Gaps are deliberately+-- not filled: reusing a retired number could make an old reference silently+-- point at a different document.+nextDocumentId :: ProfileSpec -> [Concept] -> Text -> DocumentId+nextDocumentId spec concepts requestedPrefix =+ DocumentId+ { prefix = requestedPrefix,+ number = highestNumber + 1+ }+ where+ highestNumber =+ List.foldl'+ max+ 0+ [ documentId ^. #number+ | (documentId, _) <- documentIdsInBundle spec concepts,+ documentId ^. #prefix == requestedPrefix+ ]++-- | One structural segment of a frontmatter path. EP-2 produces top-level+-- 'FieldName' paths; later bounded nested validation can append field names and+-- array indexes without encoding paths as ad hoc text.+data FieldPathSegment+ = FieldName Text+ | ArrayIndex Int+ deriving stock (Generic, Eq, Ord, Show)++newtype FieldPath = FieldPath+ { segments :: NonEmpty FieldPathSegment+ }+ deriving stock (Generic, Eq, Ord, Show)++topLevelFieldPath :: Text -> FieldPath+topLevelFieldPath key = FieldPath (FieldName key :| [])++nestedDefinitionPath :: Text -> Text -> FieldPath+nestedDefinitionPath parent child =+ FieldPath (FieldName parent :| [FieldName child])++nestedValuePath :: Text -> Int -> Text -> FieldPath+nestedValuePath parent elementIndex child =+ FieldPath (FieldName parent :| [ArrayIndex elementIndex, FieldName child])++nestedElementPath :: Text -> Int -> FieldPath+nestedElementPath parent elementIndex =+ FieldPath (FieldName parent :| [ArrayIndex elementIndex])++-- | A single deviation from a profile. Advisory by default at the CLI layer.+data ProfileViolation+ = -- | concept's @type@ is not listed in the profile and unknown types are disallowed+ ConstraintPresenceMissing ConceptId FieldPath (Maybe FieldCondition)+ | ConstraintAlternativeMissing ConceptId [FieldPath] (Maybe FieldCondition)+ | TimestampOrderViolation ConceptId FieldPath FieldPath+ | TextOperandMismatch ConceptId FieldPath Value+ | MembershipValueMissing ConceptId FieldPath Text ValueSelector+ | DuplicateSelectedValue ConceptId FieldPath Text FieldPath+ | TypeNotInProfile ConceptId Text+ | -- | a required frontmatter key is missing or empty (concept, key, activating condition)+ MissingProfileField ConceptId Text (Maybe FieldCondition)+ | -- | a recommended frontmatter key is missing under strict authoring+ MissingRecommendedProfileField ConceptId Text (Maybe FieldCondition)+ | -- | a required field is missing inside one list element record+ MissingNestedProfileField ConceptId FieldPath (Maybe FieldCondition)+ | -- | a recommended nested field is missing under strict authoring+ MissingRecommendedNestedProfileField ConceptId FieldPath (Maybe FieldCondition)+ | -- | a present value is outside the effective textual vocabulary+ ValueNotInVocabulary ConceptId FieldPath [Text] Value+ | -- | a present value has the wrong scalar/list shape+ CardinalityMismatch ConceptId FieldPath Cardinality Value+ | -- | a present value does not satisfy its named textual format+ ValueFormatMismatch ConceptId FieldPath FieldFormat Value+ | -- | a local reference has the right prefix but no valid owner in this bundle+ DanglingHandleReference ConceptId FieldPath Text+ | -- | a parsed local handle uses a prefix other than the declared target prefix+ ReferenceHandlePrefixMismatch ConceptId FieldPath Text Text+ | -- | a reference value is neither textual local handle nor valid absolute URI+ MalformedDocumentReference ConceptId FieldPath Value+ | -- | an absolute external URI uses a scheme the profile did not permit+ ExternalReferenceSchemeNotAllowed ConceptId FieldPath Text [Text]+ | -- | a syntactically valid local handle is prohibited at this field+ LocalDocumentReferenceNotAllowed ConceptId FieldPath Text+ | -- | an allowed external URI does not match the declared whole-value pattern+ ExternalReferencePatternMismatch ConceptId FieldPath Text Text+ | -- | a local handle, or a bundle path, resolves to the concept carrying it+ SelfDocumentReference ConceptId FieldPath Text+ | -- | a path-valued field's value is not one of the three shapes of §6.2+ MalformedPathReference ConceptId FieldPath Value+ | -- | a relative path climbs above the bundle root+ PathEscapesBundle ConceptId FieldPath Text+ | -- | a bundle path names a concept that does not exist in this bundle+ DanglingPathReference ConceptId FieldPath Text+ | -- | a closed profile does not declare this top-level field+ FieldNotInProfile ConceptId Text+ | -- | a closed nested shape does not declare this member+ NestedFieldNotInProfile ConceptId FieldPath+ | -- | a declared list element is not an object record+ NestedElementNotRecord ConceptId FieldPath Value+ | -- | one valid scalar member value occurs in more than one list element+ DuplicateNestedFieldValue ConceptId FieldPath Value (NonEmpty Int)+ | -- | concept's file path does not match the type rule's pattern (concept, type, pattern)+ PathPatternMismatch ConceptId Text Text+ | -- | type rule requires a resource scheme but resource is absent (concept, type, scheme)+ MissingResource ConceptId Text Text+ | -- | resource present but its scheme is wrong (concept, expected scheme, actual resource)+ ResourceSchemeMismatch ConceptId Text Text+ | -- | required @# Schema@ section is absent (concept, type)+ MissingSchemaSection ConceptId Text+ | -- | @# Schema@ table columns do not match (concept, type, expected, actual)+ SchemaColumnsMismatch ConceptId Text [Text] [Text]+ | -- | type rule declares an @idPrefix@ but the concept has no handle (concept, type, prefix)+ MissingDocumentId ConceptId Text Text+ | -- | handle present but malformed for the declared prefix (concept, prefix, actual value)+ MalformedDocumentId ConceptId Text Text+ | -- | the same handle appears on more than one concept (handle, concept, other concept)+ DuplicateDocumentId Text ConceptId ConceptId+ | -- | the profile requires the bundle to declare an OKF version it does not+ -- (required version, what the bundle declared, 'Nothing' when undeclared)+ --+ -- The only violation that belongs to the bundle rather than to a concept, so+ -- a consumer grouping violations by 'ConceptId' has nothing to key it on.+ RequiredBundleVersionUnmet Text (Maybe Text)+ deriving stock (Generic, Eq, Show)++-- | Check every concept against a compiled profile, returning all deviations.+-- Profile-wide rules apply even to unknown types; a matching type rule adds its+-- frontmatter rules and the existing type-specific structural checks.+--+-- Existence is decided against the concepts themselves, so a @path@ rule can+-- tell whether a target names a concept and cannot tell whether it names+-- @references\/attesters\/revenue.py@; such a target is accepted unchecked.+-- Callers holding a real directory should use 'validateProfileWith'.+validateProfile :: ValidationProfile -> CompiledProfile -> [Concept] -> [ProfileViolation]+validateProfile =+ validateProfileWithPresence conceptsOnly bundleInventoryOfConcepts+ where+ -- Narrowed to @.md@ on purpose. A caller with concepts and no directory can+ -- decide a @.md@ target, because a 'Concept' /is/ a non-reserved @.md@+ -- file — but it has never been able to see §6.3's+ -- @references/attesters/revenue.py@, and answering 'TargetAbsent' for it+ -- would turn a question okf cannot ask into a rejection.+ conceptsOnly conceptInventory resolved+ | FilePath.takeExtension resolved /= ".md" = TargetUnknown+ | bundleInventoryMember resolved conceptInventory = TargetPresent+ | otherwise = TargetAbsent++-- | 'validateProfile' with the bundle's full file inventory, so a @path@ rule+-- can decide whether a target that is not a concept exists.+--+-- Specification §6.3's own example of a path target is+-- @references\/attesters\/revenue.py@, which is not a concept and which+-- 'validateProfile' therefore accepts unchecked. A caller that walked a real+-- directory has 'Okf.Bundle.walkBundleInventory' and can do better. See+-- @docs\/adr\/13-the-references-convention-and-non-markdown-files.md@.+--+-- Passing an inventory does not make validation touch the filesystem. The+-- inventory is data read once during the bundle walk, where okf is already doing+-- IO, which is the shape @docs\/adr\/5-compile-profile-rules-before-validation.md@+-- requires and the one the core §6.2 check already uses.+validateProfileWith :: BundleInventory -> ValidationProfile -> CompiledProfile -> [Concept] -> [ProfileViolation]+validateProfileWith inventory =+ validateProfileWithPresence everyFile (const inventory)+ where+ everyFile fullInventory resolved+ | bundleInventoryMember resolved fullInventory = TargetPresent+ | otherwise = TargetAbsent++-- | Check a bundle's specification §12 version declaration against the profile's+-- @requireBundleVersion@ setting. Returns @[]@ when the profile requires nothing,+-- which is the default and the case for almost every profile.+--+-- Deliberately a separate entry point rather than a parameter on+-- 'validateProfile' and 'validateProfileWith'. Those two are public and their+-- signatures are depended on downstream, and this check consults no concepts at+-- all: threading a bundle-scoped question through a concept-walking function+-- would be misleading as well as breaking.+--+-- §12 makes the declaration a MAY, so okf's own validator never asks for one —+-- see @docs\/adr\/10-okf-version-declaration-and-best-effort-reading.md@. This is+-- the house-convention half: nothing is reported unless a profile author wrote+-- the field.+--+-- A declaration okf cannot parse counts as unmet. It cannot be compared, okf+-- already reports it separately as a strict-mode authoring lint, and silently+-- passing it would let a typo satisfy a requirement.+validateProfileVersion :: VersionDeclaration -> CompiledProfile -> [ProfileViolation]+validateProfileVersion declaration compiled =+ case compiledProfileRequiredBundleVersion compiled of+ Nothing -> []+ Just required ->+ let unmet = [RequiredBundleVersionUnmet (renderOkfVersion required) declaredText]+ in case declaration of+ -- A bundle ahead of the house minimum is not a deviation: §12 defines+ -- a minor bump as backward-compatible additions.+ VersionDeclared declared | declared >= required -> []+ VersionDeclared _ -> unmet+ VersionUnparseable _ -> unmet+ VersionUndeclared -> unmet+ where+ declaredText = case declaration of+ VersionDeclared declared -> Just (renderOkfVersion declared)+ VersionUnparseable rawVersion -> Just rawVersion+ VersionUndeclared -> Nothing++-- | The shared body of 'validateProfile' and 'validateProfileWith'.+--+-- The first argument decides what a @path@ rule may conclude about a resolved+-- §6.2 target; the second supplies the inventory it consults, derived from the+-- concepts when the caller has no directory to walk. Keeping one implementation+-- is what stops the two entry points from drifting apart on everything /except/+-- the one question that distinguishes them.+validateProfileWithPresence ::+ (BundleInventory -> FilePath -> PathTargetPresence) ->+ ([Concept] -> BundleInventory) ->+ ValidationProfile ->+ CompiledProfile ->+ [Concept] ->+ [ProfileViolation]+validateProfileWithPresence presenceRule inventoryOf validationProfile compiled concepts =+ concatMap checkConcept sortedConcepts <> checkDuplicateDocumentIds spec sortedConcepts+ where+ spec = compiledProfileSpec compiled+ sortedConcepts = List.sortOn (renderConceptId . conceptIdOf) concepts+ rulesByType = [(rule ^. #type_, rule) | rule <- spec ^. #types]+ validDocumentIdIndex = buildValidDocumentIdIndex spec rulesByType sortedConcepts+ -- What this caller can say about a resolved §6.2 path, built once.+ presenceOf = presenceRule (inventoryOf sortedConcepts)++ checkConcept concept =+ let cid = conceptIdOf concept+ ctype = conceptType concept+ fieldViolations = checkFields cid ctype concept <> checkUnknownFields cid ctype concept+ in case lookup ctype rulesByType of+ Nothing ->+ [TypeNotInProfile cid ctype | not (spec ^. #allowUnknownTypes)] <> fieldViolations+ Just rule ->+ fieldViolations+ <> checkPath cid ctype rule+ <> checkResource cid ctype rule concept+ <> checkSchema cid ctype rule concept+ <> checkDocumentId spec cid ctype rule concept++ checkFields cid ctype concept =+ List.nub $+ concatMap checkField (Map.toAscList (effectiveRulesForType compiled ctype))+ <> validateConceptConstraints cid topLevelFieldPath (\k -> frontmatterLookup k (conceptFrontmatter concept)) (effectiveRulesForType compiled ctype) (compiledProfileConstraintsForType compiled ctype)+ where+ checkField (key, rule) =+ case evaluateFieldValue rule (frontmatterLookup key (conceptFrontmatter concept)) of+ FieldAbsent actual ->+ presenceViolations key rule+ <> maybe [] (vocabularyViolations key rule) actual+ <> maybe [] (formatViolations key rule) actual+ <> maybe [] (referenceViolations (topLevelFieldPath key) rule) actual+ <> maybe [] (pathViolations (topLevelFieldPath key) rule) actual+ FieldPresent actual ->+ vocabularyViolations key rule actual+ <> formatViolations key rule actual+ <> referenceViolations (topLevelFieldPath key) rule actual+ <> pathViolations (topLevelFieldPath key) rule actual+ <> nestedViolations key rule actual+ <> objectViolations key rule actual+ FieldWrongShape actual -> [CardinalityMismatch cid (topLevelFieldPath key) (rule ^. #cardinality) actual]++ presenceViolations key rule =+ case applicablePresenceClause validationProfile (\sourceKey -> frontmatterLookup sourceKey (conceptFrontmatter concept)) rule of+ Nothing -> []+ Just clause ->+ [ case clause ^. #requirement of+ RequiredField -> MissingProfileField cid key (clause ^. #condition)+ RecommendedField -> MissingRecommendedProfileField cid key (clause ^. #condition)+ ]+ vocabularyViolations key rule actual =+ [ ValueNotInVocabulary cid (topLevelFieldPath key) (rule ^. #allowedValues) actual+ | not (null (rule ^. #allowedValues)),+ not (valueMatchesVocabulary (rule ^. #allowedValues) actual)+ ]+ formatViolations key rule actual =+ [ ValueFormatMismatch cid (topLevelFieldPath key) fieldFormat actual+ | Just fieldFormat <- [rule ^. #format],+ not (valueMatchesFormat fieldFormat actual)+ ]+ referenceViolations fieldPath rule actual =+ case rule ^. #reference of+ Nothing -> []+ Just policy -> validateReferenceValue validDocumentIdIndex cid fieldPath policy (rule ^. #compiledExternalUriPattern) actual++ -- Takes a 'FieldPath' rather than a key because it is shared by all+ -- three scopes: a top-level key, a member of a list element, and a+ -- member of an object-valued mapping.+ pathViolations fieldPath rule actual =+ case rule ^. #path of+ Nothing -> []+ Just policy -> validatePathValue presenceOf cid fieldPath policy actual++ nestedViolations parentKey parentRule = \case+ Array elementValues+ | Just nestedRules <- parentRule ^. #elementFields ->+ let indexedElements = zip [0 ..] (Vector.toList elementValues)+ in concat+ [ case elementValue of+ Aeson.Object members ->+ unknownRecordMembers (nestedValuePath parentKey elementIndex) nestedRules (parentRule ^. #elementAllowsUnknownFields) members+ <> concatMap+ (checkRecordMember (nestedValuePath parentKey elementIndex) members)+ (Map.toAscList nestedRules)+ <> validateConceptConstraints cid (nestedValuePath parentKey elementIndex) (\k -> Aeson.KeyMap.lookup (Aeson.Key.fromText k) members) nestedRules (parentRule ^. #elementConstraints)+ _ -> [NestedElementNotRecord cid (nestedElementPath parentKey elementIndex) elementValue]+ | (elementIndex, elementValue) <- indexedElements+ ]+ <> uniquenessViolations parentKey parentRule nestedRules indexedElements+ _ -> []++ -- The mapping spelling of the same idea. The value /is/ the record, so+ -- there is no index in the path: a member is reported as+ -- @generated.by@ rather than @generated[0].by@.+ objectViolations parentKey parentRule = \case+ Aeson.Object members+ | Just objectRules <- parentRule ^. #objectFields ->+ unknownRecordMembers (nestedDefinitionPath parentKey) objectRules (parentRule ^. #objectAllowsUnknownFields) members+ <> concatMap+ (checkRecordMember (nestedDefinitionPath parentKey) members)+ (Map.toAscList objectRules)+ <> validateConceptConstraints cid (nestedDefinitionPath parentKey) (\k -> Aeson.KeyMap.lookup (Aeson.Key.fromText k) members) objectRules (parentRule ^. #objectConstraints)+ _ -> []++ unknownRecordMembers buildPath rules policy members =+ [ NestedFieldNotInProfile cid (buildPath key)+ | policy == Just False,+ key <- List.sort (map Aeson.Key.toText (Aeson.KeyMap.keys members)),+ Map.notMember key rules+ ]++ -- Check one member of one record. Shared by the list-element and+ -- object-value walks, which differ only in how they name the member;+ -- the sibling lookup that resolves a @when@ condition stays scoped to+ -- the record the member lives in either way.+ checkRecordMember buildPath members (key, rule) =+ let path = buildPath key+ actualValue = Aeson.KeyMap.lookup (Aeson.Key.fromText key) members+ in case evaluateFieldValue rule actualValue of+ FieldAbsent actual ->+ nestedPresenceViolations members path rule+ <> maybe [] (nestedVocabularyViolations path rule) actual+ <> maybe [] (nestedFormatViolations path rule) actual+ <> maybe [] (referenceViolations path rule) actual+ <> maybe [] (pathViolations path rule) actual+ FieldPresent actual ->+ nestedVocabularyViolations path rule actual+ <> nestedFormatViolations path rule actual+ <> referenceViolations path rule actual+ <> pathViolations path rule actual+ FieldWrongShape actual -> [CardinalityMismatch cid path (rule ^. #cardinality) actual]++ nestedPresenceViolations objectFields path rule =+ case applicablePresenceClause validationProfile lookupSibling rule of+ Nothing -> []+ Just clause ->+ [ case clause ^. #requirement of+ RequiredField -> MissingNestedProfileField cid path (clause ^. #condition)+ RecommendedField -> MissingRecommendedNestedProfileField cid path (clause ^. #condition)+ ]+ where+ lookupSibling sourceKey = Aeson.KeyMap.lookup (Aeson.Key.fromText sourceKey) objectFields+ nestedVocabularyViolations path rule actual =+ [ ValueNotInVocabulary cid path (rule ^. #allowedValues) actual+ | not (null (rule ^. #allowedValues)),+ not (valueMatchesVocabulary (rule ^. #allowedValues) actual)+ ]+ nestedFormatViolations path rule actual =+ [ ValueFormatMismatch cid path fieldFormat actual+ | Just fieldFormat <- [rule ^. #format],+ not (valueMatchesFormat fieldFormat actual)+ ]++ uniquenessViolations parentKey parentRule nestedRules indexedElements =+ case parentRule ^. #uniqueBy of+ Nothing -> []+ Just key ->+ case Map.lookup key nestedRules of+ Nothing -> []+ Just keyRule ->+ [ DuplicateNestedFieldValue cid (nestedDefinitionPath parentKey key) duplicateValue (firstIndex :| remainingIndices)+ | (duplicateValue, firstIndex : remainingIndices) <- groupedValues key keyRule indexedElements,+ not (null remainingIndices)+ ]++ groupedValues key keyRule =+ List.foldl' insertValue [] . mapMaybe participant+ where+ participant (elementIndex, Aeson.Object members) =+ case evaluateFieldValue keyRule (Aeson.KeyMap.lookup (Aeson.Key.fromText key) members) of+ FieldPresent scalarValue -> Just (scalarValue, elementIndex)+ _ -> Nothing+ participant _ = Nothing++ insertValue [] (value, elementIndex) = [(value, [elementIndex])]+ insertValue ((groupValue, elementIndices) : remaining) (value, elementIndex)+ | groupValue == value = (groupValue, elementIndices <> [elementIndex]) : remaining+ | otherwise = (groupValue, elementIndices) : insertValue remaining (value, elementIndex)++ checkUnknownFields cid ctype concept+ | spec ^. #allowUnknownFields = []+ | otherwise =+ [ FieldNotInProfile cid key+ | key <- frontmatterKeys (conceptFrontmatter concept),+ key `Set.notMember` allowedFields ctype+ ]++ allowedFields ctype =+ coreFrontmatterFields+ <> Map.keysSet (effectiveRulesForType compiled ctype)+ <> maybe Set.empty Set.singleton (spec ^. #idField)++-- | Index only handles that are valid owners under the compiled profile's raw+-- type declarations: the concept has a matching type rule, that rule declares+-- the parsed handle's exact prefix, and the profile declares the owning field.+-- Multiple owners are retained so a duplicate target still counts as existing;+-- the existing bundle-wide duplicate diagnostic remains authoritative.+buildValidDocumentIdIndex :: ProfileSpec -> [(Text, TypeRule)] -> [Concept] -> Map DocumentId [ConceptId]+buildValidDocumentIdIndex spec rulesByType concepts =+ case spec ^. #idField of+ Nothing -> Map.empty+ Just fieldName ->+ Map.fromListWith+ (flip (<>))+ [ (documentId, [conceptIdOf concept])+ | concept <- concepts,+ Just typeRule <- [lookup (conceptType concept) rulesByType],+ Just expectedPrefix <- [typeRule ^. #idPrefix],+ Just (String rawDocumentId) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],+ Just documentId <- [parseDocumentId rawDocumentId],+ documentId ^. #prefix == expectedPrefix+ ]++validateReferenceValue :: Map DocumentId [ConceptId] -> ConceptId -> FieldPath -> HandleReferenceRule -> Maybe Regex -> Value -> [ProfileViolation]+validateReferenceValue validOwners sourceConcept path policy matcher = \case+ String rawReference -> validateReferenceText validOwners sourceConcept path policy matcher rawReference+ Array values ->+ concat+ [ case value of+ String rawReference -> validateReferenceText validOwners sourceConcept (appendArrayIndex path elementIndex) policy matcher rawReference+ _ -> [MalformedDocumentReference sourceConcept (appendArrayIndex path elementIndex) value]+ | (elementIndex, value) <- zip [0 ..] (Vector.toList values)+ ]+ actual -> [MalformedDocumentReference sourceConcept path actual]++validateReferenceText :: Map DocumentId [ConceptId] -> ConceptId -> FieldPath -> HandleReferenceRule -> Maybe Regex -> Text -> [ProfileViolation]+validateReferenceText validOwners sourceConcept path policy matcher rawReference =+ case parseDocumentId rawReference of+ Just documentId+ | not (policy ^. #allowLocal) ->+ [LocalDocumentReferenceNotAllowed sourceConcept path rawReference]+ | documentId ^. #prefix /= policy ^. #localPrefix ->+ [ReferenceHandlePrefixMismatch sourceConcept path rawReference (policy ^. #localPrefix)]+ | otherwise ->+ case Map.lookup documentId validOwners of+ Nothing -> [DanglingHandleReference sourceConcept path rawReference]+ Just owners+ | not (policy ^. #allowSelf),+ sourceConcept `elem` owners ->+ [SelfDocumentReference sourceConcept path rawReference]+ | otherwise -> []+ Nothing ->+ case parseURI (Text.unpack rawReference) of+ Just parsed+ | not (Text.null normalizedScheme),+ normalizedScheme `elem` policy ^. #externalUriSchemes ->+ case (policy ^. #externalUriPattern, matcher) of+ (Nothing, _) -> []+ (Just patternText, Just compiledPattern)+ | matchesWholeExternalUriPattern compiledPattern rawReference -> []+ | otherwise -> [ExternalReferencePatternMismatch sourceConcept path rawReference patternText]+ (Just patternText, Nothing) -> [ExternalReferencePatternMismatch sourceConcept path rawReference patternText]+ | not (Text.null normalizedScheme) ->+ [ ExternalReferenceSchemeNotAllowed+ sourceConcept+ path+ normalizedScheme+ (policy ^. #externalUriSchemes)+ ]+ | otherwise -> [MalformedDocumentReference sourceConcept path (String rawReference)]+ where+ normalizedScheme = Text.toCaseFold (Text.dropWhileEnd (== ':') (Text.pack (uriScheme parsed)))+ Nothing -> [MalformedDocumentReference sourceConcept path (String rawReference)]++-- | Check one path-valued frontmatter value, at any scope. A list is checked+-- element-wise so that a diagnostic names @sources[1].resource@ rather than the+-- whole list.+validatePathValue :: (FilePath -> PathTargetPresence) -> ConceptId -> FieldPath -> PathReferenceRule -> Value -> [ProfileViolation]+validatePathValue presenceOf sourceConcept path policy = \case+ String rawPath -> validatePathText presenceOf sourceConcept path policy rawPath+ Array values ->+ concat+ [ case value of+ String rawPath ->+ validatePathText presenceOf sourceConcept (appendArrayIndex path elementIndex) policy rawPath+ _ -> [MalformedPathReference sourceConcept (appendArrayIndex path elementIndex) value]+ | (elementIndex, value) <- zip [0 ..] (Vector.toList values)+ ]+ actual -> [MalformedPathReference sourceConcept path actual]++-- | What a caller can say about a resolved §6.2 bundle path.+--+-- Three answers rather than two, because "I cannot tell" is a real state and+-- collapsing it into "absent" turns silence into a rejection. 'validateProfile'+-- is handed concepts alone, so it genuinely cannot say whether §6.3's+-- @references\/attesters\/revenue.py@ is there, and reporting it as dangling+-- would be a claim okf did not check.+data PathTargetPresence+ = -- | The bundle holds it.+ TargetPresent+ | -- | The bundle does not hold it, and the caller can see enough to be sure.+ TargetAbsent+ | -- | The caller cannot answer for this path.+ TargetUnknown+ deriving stock (Generic, Eq, Show)++-- | Check one path value against the §6.2 grammar and the profile's policy.+--+-- Every diagnostic carries the raw text the author wrote rather than the+-- collapsed path okf computed from it, so the message names something findable+-- in the file.+--+-- Existence is decided by the supplied lookup, which reports what the caller can+-- actually see: 'validateProfileWith' holds a real directory's inventory and so+-- resolves §6.3's own @references\/attesters\/revenue.py@, while+-- 'validateProfile' holds the concepts alone and answers 'TargetUnknown' for it.+--+-- The @.md@ branch still goes through 'conceptIdFromFilePath', because that is+-- what decides self-reference and what makes 'MalformedPathReference' reachable+-- for a path no concept ID could be built from. What it no longer decides is+-- whether the target is there.+validatePathText :: (FilePath -> PathTargetPresence) -> ConceptId -> FieldPath -> PathReferenceRule -> Text -> [ProfileViolation]+validatePathText presenceOf sourceConcept path policy rawPath =+ case classifyPathReference sourceConcept rawPath of+ ExternalUrl scheme+ | scheme `elem` policy ^. #externalUriSchemes -> []+ | otherwise ->+ [ExternalReferenceSchemeNotAllowed sourceConcept path scheme (policy ^. #externalUriSchemes)]+ EscapesBundle -> [PathEscapesBundle sourceConcept path rawPath]+ MalformedPath -> [MalformedPathReference sourceConcept path (String rawPath)]+ BundlePath resolved+ | FilePath.takeExtension resolved == ".md" ->+ case conceptIdFromFilePath resolved of+ Left _ -> [MalformedPathReference sourceConcept path (String rawPath)]+ Right target+ | target == sourceConcept,+ not (policy ^. #allowSelf) ->+ [SelfDocumentReference sourceConcept path rawPath]+ | otherwise -> danglingWhenAbsent resolved+ | otherwise -> danglingWhenAbsent resolved+ where+ danglingWhenAbsent resolved = case presenceOf resolved of+ TargetAbsent -> [DanglingPathReference sourceConcept path rawPath]+ TargetPresent -> []+ TargetUnknown -> []++appendArrayIndex :: FieldPath -> Int -> FieldPath+appendArrayIndex (FieldPath pathSegments) elementIndex =+ FieldPath (pathSegments <> (ArrayIndex elementIndex :| []))++applicablePresenceClause :: ValidationProfile -> (Text -> Maybe Value) -> EffectiveFieldRule -> Maybe PresenceClause+applicablePresenceClause validationProfile lookupValue rule =+ List.find applies requiredClauses+ <|> if validationProfile == StrictAuthoring then List.find applies recommendedClauses else Nothing+ where+ requiredClauses = filter ((== RequiredField) . (^. #requirement)) (rule ^. #presenceClauses)+ recommendedClauses = filter ((== RecommendedField) . (^. #requirement)) (rule ^. #presenceClauses)+ applies clause = maybe True conditionApplies (clause ^. #condition)+ conditionApplies condition =+ case lookupValue (condition ^. #field) of+ Just (String actual) -> actual `elem` condition ^. #hasValue+ _ -> False++valueMatchesVocabulary :: [Text] -> Value -> Bool+valueMatchesVocabulary allowed = \case+ String value -> value `elem` allowed+ Array values -> all elementMatches (Vector.toList values)+ _ -> False+ where+ elementMatches (String value) = value `elem` allowed+ elementMatches _ = False++-- | Whether a present value satisfies a format. Each format declares which+-- value shapes it can match at all: the textual formats match a string, the+-- numeric ones a YAML number, and 'Boolean' a YAML boolean. A list is matched+-- by recursing, so a list of integers satisfies an @integer@ format for the+-- same reason a list of timestamps satisfies @rfc3339-utc@ — a format+-- constrains a value, and a list is a list of values.+valueMatchesFormat :: FieldFormat -> Value -> Bool+valueMatchesFormat fieldFormat actual =+ case actual of+ String value -> textMatchesFormat fieldFormat value+ Array values -> all (valueMatchesFormat fieldFormat) (Vector.toList values)+ Number _ -> fieldFormat == FiniteDecimal || numberMatchesFormat fieldFormat actual+ Bool _ -> fieldFormat == Boolean+ _ -> False++textMatchesFormat :: FieldFormat -> Text -> Bool+textMatchesFormat fieldFormat value =+ case fieldFormat of+ Rfc3339Utc ->+ hasExtendedUtcShape value+ && isJust (iso8601ParseM (Text.unpack value) :: Maybe UTCTime)+ Date ->+ hasExtendedDateShape value+ && isJust (iso8601ParseM (Text.unpack value) :: Maybe Day)+ Uri -> isJust (parseURI (Text.unpack value))+ UriWithScheme expectedScheme ->+ case parseURI (Text.unpack value) of+ Just parsed ->+ Text.toCaseFold (Text.dropWhileEnd (== ':') (Text.pack (uriScheme parsed)))+ == Text.toCaseFold expectedScheme+ Nothing -> False+ DocumentHandle expectedPrefix ->+ case parseDocumentId value of+ Just documentId -> documentId ^. #prefix == expectedPrefix+ Nothing -> False+ -- Specification §7 defines exactly three actor shapes, and+ -- 'Actor.parseActor' classifies them, so the format is a case match on its+ -- result rather than a second parser. A value the convention does not+ -- define — the specification's own illustrative @team:ga4-docs@ among them —+ -- is 'Actor.UnclassifiedActor' and is reported.+ Actor ->+ case Actor.parseActor value of+ Actor.UnclassifiedActor _ -> False+ _ -> True+ HumanActor -> Actor.isHumanActor (Actor.parseActor value)+ -- A numeric or boolean format never matches text. A @usage_count: "5000"@+ -- is a quoted string in YAML and is reported rather than coerced, which is+ -- the whole point of being able to declare the format.+ Integer -> False+ NonNegativeInteger -> False+ FiniteDecimal -> False+ Boolean -> False++-- | Whether a YAML number satisfies a numeric format. Uses aeson's own+-- 'Integer' decoder, which already rejects a non-integral number, rather than+-- reaching for @scientific@: that package is a dependency of @aeson@ but not of+-- @okf-core@, so it is importable in a scratch experiment and not from here.+-- 'Okf.Document.objectInteger' takes the same route.+numberMatchesFormat :: FieldFormat -> Value -> Bool+numberMatchesFormat fieldFormat actual =+ case Aeson.fromJSON actual :: Aeson.Result Integer of+ Aeson.Error _ -> False+ Aeson.Success parsed ->+ case fieldFormat of+ Integer -> True+ NonNegativeInteger -> parsed >= 0+ _ -> False++hasExtendedDateShape :: Text -> Bool+hasExtendedDateShape value =+ Text.length value == 10+ && Text.index value 4 == '-'+ && Text.index value 7 == '-'++hasExtendedUtcShape :: Text -> Bool+hasExtendedUtcShape value =+ Text.length value >= 20+ && Text.index value 4 == '-'+ && Text.index value 7 == '-'+ && Text.index value 10 == 'T'+ && Text.index value 13 == ':'+ && Text.index value 16 == ':'+ && Text.last value == 'Z'++data FieldValueEvaluation+ = FieldAbsent (Maybe Value)+ | FieldPresent Value+ | FieldWrongShape Value++-- | Decide whether a frontmatter value counts as present for one rule. Takes the+-- whole rule rather than just its cardinality because the 'Any' case has to know+-- whether the rule declares object members: a rule accepting both spellings of+-- the OKF v0.2 @verified@ key stays at 'Any' cardinality and must still count a+-- mapping as present.+--+-- This is the single point at which okf decides presence, so a change here+-- affects every profile check.+evaluateFieldValue :: EffectiveFieldRule -> Maybe Value -> FieldValueEvaluation+evaluateFieldValue _ Nothing = FieldAbsent Nothing+evaluateFieldValue rule (Just actual) =+ case rule ^. #cardinality of+ Any+ | legacyValueIsPresent actual -> FieldPresent actual+ -- A rule that also accepts a mapping counts a non-empty one as present.+ -- Without this a profile declaring both shapes would report `verified`+ -- missing on a document that writes it as a bare mapping.+ | isJust (rule ^. #objectFields), nonEmptyObject actual -> FieldPresent actual+ | otherwise -> FieldAbsent (Just actual)+ Scalar ->+ case actual of+ String value+ | Text.null (Text.strip value) -> FieldAbsent (Just actual)+ | otherwise -> FieldPresent actual+ Number _ -> FieldPresent actual+ Bool _ -> FieldPresent actual+ _ -> FieldWrongShape actual+ List ->+ case actual of+ Array values+ | null values -> FieldAbsent (Just actual)+ | otherwise -> FieldPresent actual+ _ -> FieldWrongShape actual+ Object+ -- An empty mapping is treated as absent for the same reason an empty list+ -- is: a key written with no content is the author saying nothing, and+ -- reporting it missing is more useful than reporting it present and empty.+ | nonEmptyObject actual -> FieldPresent actual+ | isObject actual -> FieldAbsent (Just actual)+ | otherwise -> FieldWrongShape actual++isObject :: Value -> Bool+isObject = \case+ Aeson.Object _ -> True+ _ -> False++nonEmptyObject :: Value -> Bool+nonEmptyObject = \case+ Aeson.Object members -> not (Aeson.KeyMap.null members)+ _ -> False++legacyValueIsPresent :: Value -> Bool+legacyValueIsPresent = \case+ String value -> not (Text.null (Text.strip value))+ Array values -> not (null values)+ _ -> False++-- | Check a profile-declared document ID for one concept.+checkDocumentId :: ProfileSpec -> ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]+checkDocumentId spec cid ctype rule concept =+ case (spec ^. #idField, rule ^. #idPrefix) of+ (Just fieldName, Just expectedPrefix) ->+ case frontmatterLookup fieldName (conceptFrontmatter concept) of+ Just (String value)+ | not (Text.null (Text.strip value)) ->+ case parseDocumentId value of+ Just documentId+ | documentId ^. #prefix == expectedPrefix -> []+ _ -> [MalformedDocumentId cid expectedPrefix value]+ _ -> [MissingDocumentId cid ctype expectedPrefix]+ _ -> []++-- | Check every non-empty value under the profile's ID field for bundle-wide+-- uniqueness. Concept IDs are sorted before grouping so output is deterministic.+checkDuplicateDocumentIds :: ProfileSpec -> [Concept] -> [ProfileViolation]+checkDuplicateDocumentIds spec concepts =+ case spec ^. #idField of+ Nothing -> []+ Just fieldName ->+ concatMap duplicateViolations (groupedHandles fieldName)+ where+ groupedHandles fieldName =+ List.groupBy+ (\(leftHandle, _) (rightHandle, _) -> leftHandle == rightHandle)+ (handles fieldName)+ handles fieldName =+ List.sortOn+ (\(handle, cid) -> (handle, renderConceptId cid))+ [ (handle, conceptIdOf concept)+ | concept <- concepts,+ Just (String handle) <- [frontmatterLookup fieldName (conceptFrontmatter concept)],+ not (Text.null (Text.strip handle))+ ]+ duplicateViolations ((handle, firstConcept) : duplicates) =+ [ DuplicateDocumentId handle firstConcept duplicateConcept+ | (_, duplicateConcept) <- duplicates+ ]+ duplicateViolations [] = []++-- | Project a concept's frontmatter (the document's @frontmatter@ field).+conceptFrontmatter :: Concept -> Frontmatter+conceptFrontmatter concept = conceptDocument concept ^. #frontmatter++-- | A type rule's @pathPattern@, when present, constrains where the concept's+-- file may live.+checkPath :: ConceptId -> Text -> TypeRule -> [ProfileViolation]+checkPath cid ctype rule =+ case rule ^. #pathPattern of+ Nothing -> []+ Just patternText+ | matchPathPattern patternText cid -> []+ | otherwise -> [PathPatternMismatch cid ctype patternText]++-- | Match a concept ID against a segment-glob pattern. @*@ matches exactly one+-- segment; a single trailing @**@ matches one or more remaining segments; every+-- other segment matches literally. Both segment lists must be consumed exactly,+-- except for the trailing @**@ case.+matchPathPattern :: Text -> ConceptId -> Bool+matchPathPattern patternText cid =+ go (Text.splitOn "/" patternText) (Text.splitOn "/" (renderConceptId cid))+ where+ go [] [] = True+ go ["**"] (_ : _) = True+ go ("*" : ps) (_ : ss) = go ps ss+ go (p : ps) (s : ss) = p == s && go ps ss+ go _ _ = False++-- | A type rule's @resourceScheme@, when present, requires a @resource:@ value+-- whose scheme matches.+checkResource :: ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]+checkResource cid ctype rule concept =+ case rule ^. #resourceScheme of+ Nothing -> []+ Just scheme ->+ case conceptResource concept of+ Nothing -> [MissingResource cid ctype scheme]+ Just value+ | (scheme <> "://") `Text.isPrefixOf` value -> []+ | otherwise -> [ResourceSchemeMismatch cid scheme value]++-- | A type rule's @# Schema@ contract: when @requireSchemaSection@ is set, the+-- body must contain a @# Schema@ section whose table header begins with the+-- required @schemaColumns@ (case-insensitive, trimmed, compared as a prefix so a+-- team may add trailing columns without tripping the check).+checkSchema :: ConceptId -> Text -> TypeRule -> Concept -> [ProfileViolation]+checkSchema cid ctype rule concept+ | not (rule ^. #requireSchemaSection) = []+ | otherwise =+ case schemaSectionColumns (conceptDocument concept ^. #body) of+ Nothing -> [MissingSchemaSection cid ctype]+ Just actual ->+ let expected = rule ^. #schemaColumns+ norm = map (Text.toLower . Text.strip)+ in [ SchemaColumnsMismatch cid ctype expected actual+ | not (norm expected `List.isPrefixOf` norm actual)+ ]++-- | The header-row columns of the first GitHub-flavored table that follows the+-- first top-level @# Schema@ heading, or 'Nothing' if there is no Schema heading+-- or no following table. Columns are trimmed.+schemaSectionColumns :: Text -> Maybe [Text]+schemaSectionColumns markdown =+ let CMarkGFM.Node _ _ topLevel = CMarkGFM.commonmarkToNode markdownOptions [CMarkGFM.extTable] markdown+ in firstTableAfterSchema topLevel++firstTableAfterSchema :: [CMarkGFM.Node] -> Maybe [Text]+firstTableAfterSchema topLevel =+ case dropWhile (not . isSchemaHeading) topLevel of+ (_heading : rest) -> headerRow rest+ [] -> Nothing+ where+ isSchemaHeading (CMarkGFM.Node _ (CMarkGFM.HEADING _) inner) =+ Text.toLower (Text.strip (nodeText inner)) == "schema"+ isSchemaHeading _ = False++ headerRow [] = Nothing+ headerRow (CMarkGFM.Node _ (CMarkGFM.TABLE _) tableChildren : _) =+ case tableChildren of+ (CMarkGFM.Node _ CMarkGFM.TABLE_ROW cells : _) -> Just (map cellText cells)+ _ -> Nothing+ headerRow (_ : more) = headerRow more++ cellText (CMarkGFM.Node _ _ inner) = Text.strip (nodeText inner)++-- | Concatenate all @TEXT@/@CODE@ literals under a node list, recursively.+nodeText :: [CMarkGFM.Node] -> Text+nodeText = foldMap go+ where+ go (CMarkGFM.Node _ (CMarkGFM.TEXT t) _) = t+ go (CMarkGFM.Node _ (CMarkGFM.CODE t) _) = t+ go (CMarkGFM.Node _ _ inner) = nodeText inner++containsProductDiscoveryField :: Expr Src Void -> Bool+containsProductDiscoveryField = anyOf (cosmosOf Dhall.Core.subExpressions) $ \case+ Dhall.Core.RecordLit fields ->+ any+ (`Dhall.Map.member` fields)+ ["presenceConstraints", "timestampOrderConstraints", "membershipConstraints", "uniqueValuesConstraints"]+ _ -> False++-- | Bounded locations; operators determine whether to observe the whole value+-- or project textual occurrences from a leaf list.+data ValueSelector = ValueSelector {field :: !Text, member :: !(Maybe Text)}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall, ToJSON)++data PresenceConstraint = PresenceConstraint+ { when :: !(Maybe FieldCondition),+ whenAnyPresent :: ![ValueSelector],+ requireAll :: ![ValueSelector],+ requireAny :: ![ValueSelector]+ }+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall, ToJSON)++data TimestampOrderConstraint = TimestampOrderConstraint {before :: !Text, after :: !Text}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall, ToJSON)++data MembershipConstraint = MembershipConstraint {source :: !ValueSelector, target :: !ValueSelector}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall, ToJSON)++newtype UniqueValuesConstraint = UniqueValuesConstraint {selectors :: [ValueSelector]}+ deriving stock (Generic, Eq, Ord, Show)+ deriving anyclass (FromDhall, ToJSON)++-- | Opaque conjunction of constraints, in declaration order within each kind.+data EffectiveConceptConstraints = EffectiveConceptConstraints+ { presenceConstraints :: ![PresenceConstraint],+ timestampOrderConstraints :: ![TimestampOrderConstraint],+ membershipConstraints :: ![MembershipConstraint],+ uniqueValuesConstraints :: ![UniqueValuesConstraint],+ resolvedSelectors :: !(Map ValueSelector EffectiveFieldRule)+ }+ deriving stock (Generic, Eq, Show)++conceptPresenceConstraints :: EffectiveConceptConstraints -> [PresenceConstraint]+conceptPresenceConstraints c = c ^. #presenceConstraints++conceptTimestampOrderConstraints :: EffectiveConceptConstraints -> [TimestampOrderConstraint]+conceptTimestampOrderConstraints c = c ^. #timestampOrderConstraints++conceptMembershipConstraints :: EffectiveConceptConstraints -> [MembershipConstraint]+conceptMembershipConstraints c = c ^. #membershipConstraints++conceptUniqueValuesConstraints :: EffectiveConceptConstraints -> [UniqueValuesConstraint]+conceptUniqueValuesConstraints c = c ^. #uniqueValuesConstraints++emptyConstraints :: EffectiveConceptConstraints+emptyConstraints = EffectiveConceptConstraints [] [] [] [] Map.empty++frontmatterConstraints :: FrontmatterRules -> EffectiveConceptConstraints+frontmatterConstraints r = EffectiveConceptConstraints (List.nub (r ^. #presenceConstraints)) (List.nub (r ^. #timestampOrderConstraints)) (List.nub (r ^. #membershipConstraints)) (List.nub (r ^. #uniqueValuesConstraints)) Map.empty++nestedConstraints :: NestedRules -> EffectiveConceptConstraints+nestedConstraints r = EffectiveConceptConstraints (List.nub (r ^. #presenceConstraints)) (List.nub (r ^. #timestampOrderConstraints)) [] [] Map.empty++mergeConstraints :: EffectiveConceptConstraints -> EffectiveConceptConstraints -> EffectiveConceptConstraints+mergeConstraints a b =+ EffectiveConceptConstraints+ (List.nub (a ^. #presenceConstraints <> b ^. #presenceConstraints))+ (List.nub (a ^. #timestampOrderConstraints <> b ^. #timestampOrderConstraints))+ (List.nub (a ^. #membershipConstraints <> b ^. #membershipConstraints))+ (List.nub (a ^. #uniqueValuesConstraints <> b ^. #uniqueValuesConstraints))+ Map.empty++compiledProfileBaseConstraints :: CompiledProfile -> EffectiveConceptConstraints+compiledProfileBaseConstraints c = c ^. #baseConstraints++compiledProfileConstraintsForType :: CompiledProfile -> Text -> EffectiveConceptConstraints+compiledProfileConstraintsForType c t = Map.findWithDefault (c ^. #baseConstraints) t (c ^. #constraintsByType)++fieldRuleElementConstraints :: EffectiveFieldRule -> EffectiveConceptConstraints+fieldRuleElementConstraints r = r ^. #elementConstraints++fieldRuleObjectConstraints :: EffectiveFieldRule -> EffectiveConceptConstraints+fieldRuleObjectConstraints r = r ^. #objectConstraints++-- | Resolve a selector against the effective map, never against raw declarations.+resolveValueSelector :: Map Text EffectiveFieldRule -> ValueSelector -> Maybe EffectiveFieldRule+resolveValueSelector rules ValueSelector {field = key, member} = do+ parent <- Map.lookup key rules+ case member of+ Nothing -> Just parent+ Just child -> (parent ^. #elementFields <|> parent ^. #objectFields) >>= Map.lookup child++constraintErrors :: Maybe Text -> Maybe Text -> Map Text EffectiveFieldRule -> EffectiveConceptConstraints -> [ProfileDefinitionError]+constraintErrors scope parent rules c =+ concatMap checkPresence (c ^. #presenceConstraints)+ <> concatMap checkTime (c ^. #timestampOrderConstraints)+ <> concatMap checkMembership (c ^. #membershipConstraints)+ <> concatMap checkUnique (c ^. #uniqueValuesConstraints)+ <> concat+ [ constraintErrors scope (Just key) members constraints+ | (key, r) <- Map.toList rules,+ (Just members, constraints) <- [(r ^. #elementFields, r ^. #elementConstraints), (r ^. #objectFields, r ^. #objectConstraints)]+ ]+ where+ err sel reason = [InvalidConceptConstraint scope parent sel reason]+ nameOK t = not (Text.null (Text.strip t)) && not (Text.any (`elem` (".[]" :: String)) t)+ checkSelector textOnly target sel =+ if not (nameOK (sel ^. #field)) || maybe False (not . nameOK) (sel ^. #member)+ then err sel "selector names must be nonblank field names without traversal"+ else+ if (isJust parent || target) && isJust (sel ^. #member)+ then err sel "this scope requires a sibling selector"+ else case resolveValueSelector rules sel of+ Nothing -> err sel "selector is not declared"+ Just r ->+ [ InvalidConceptConstraint scope parent sel "relation operand must be a scalar or list text leaf"+ | textOnly && (r ^. #cardinality `notElem` [Scalar, List] || isJust (r ^. #elementFields) || isJust (r ^. #objectFields) || r ^. #format `elem` map Just [Integer, NonNegativeInteger, FiniteDecimal, Boolean])+ ]+ checkPresence p =+ [InvalidConceptConstraint scope parent (ValueSelector "" Nothing) "presence constraint needs a target" | null (p ^. #requireAll <> p ^. #requireAny)]+ <> concatMap (checkSelector False False) (p ^. #whenAnyPresent)+ <> concatMap (checkSelector False True) (p ^. #requireAll <> p ^. #requireAny)+ <> concat [err s "duplicate presence selector" | xs <- [p ^. #whenAnyPresent, p ^. #requireAll, p ^. #requireAny], s <- List.nub xs, length (filter (== s) xs) > 1]+ <> case p ^. #when of+ Nothing -> []+ Just gate ->+ let sel = ValueSelector (gate ^. #field) Nothing+ in case Map.lookup (gate ^. #field) rules of+ Just r | r ^. #cardinality == Scalar, not (null (r ^. #allowedValues)), not (null (gate ^. #hasValue)), all (`elem` r ^. #allowedValues) (gate ^. #hasValue) -> []+ _ -> err sel "when requires a declared scalar closed vocabulary and reachable nonempty values"+ checkTime t =+ let a = ValueSelector (t ^. #before) Nothing; b = ValueSelector (t ^. #after) Nothing+ in concatMap (checkSelector False True) [a, b]+ <> [ InvalidConceptConstraint scope parent a "timestamp endpoints must be distinct scalar rfc3339-utc fields"+ | a == b || any (\sel -> maybe True (\r -> r ^. #cardinality /= Scalar || r ^. #format /= Just Rfc3339Utc) (resolveValueSelector rules sel)) [a, b]+ ]+ checkMembership m =+ concatMap (checkSelector True False) [m ^. #source, m ^. #target]+ <> [InvalidConceptConstraint scope parent (m ^. #source) "membership operands must differ" | m ^. #source == m ^. #target]+ checkUnique u =+ concatMap (checkSelector True False) (u ^. #selectors)+ <> [InvalidConceptConstraint scope parent (ValueSelector "" Nothing) "uniqueness needs at least two distinct selectors" | length (u ^. #selectors) < 2 || length (List.nub (u ^. #selectors)) /= length (u ^. #selectors)]++-- | Whole located values, preserving both parent-record and leaf-list indices.+locateSelector :: (Text -> FieldPath) -> (Text -> Maybe Value) -> Map Text EffectiveFieldRule -> ValueSelector -> [(FieldPath, EffectiveFieldRule, Value)]+locateSelector build lookupValue rules sel =+ case (Map.lookup key rules, lookupValue key, sel ^. #member) of+ (Just r, Just value, Nothing) -> [(build key, r, value)]+ (Just r, Just (Aeson.Object members), Just child) | Just rs <- r ^. #objectFields -> childValue (appendPath (build key) (FieldName child)) rs child members+ (Just r, Just (Array values), Just child)+ | Just rs <- r ^. #elementFields ->+ concat [childValue (appendPath (appendPath (build key) (ArrayIndex i)) (FieldName child)) rs child members | (i, Aeson.Object members) <- zip [0 ..] (Vector.toList values)]+ _ -> []+ where+ key = sel ^. #field+ childValue path rs child members = [(path, r, v) | Just r <- [Map.lookup child rs], Just v <- [Aeson.KeyMap.lookup (Aeson.Key.fromText child) members]]++appendPath :: FieldPath -> FieldPathSegment -> FieldPath+appendPath (FieldPath xs) x = FieldPath (xs <> (x :| []))++validateConceptConstraints :: ConceptId -> (Text -> FieldPath) -> (Text -> Maybe Value) -> Map Text EffectiveFieldRule -> EffectiveConceptConstraints -> [ProfileViolation]+validateConceptConstraints cid build lookupValue rules c =+ List.nub $+ concatMap presence (c ^. #presenceConstraints)+ <> concatMap timeOrder (c ^. #timestampOrderConstraints)+ <> concatMap membership (c ^. #membershipConstraints)+ <> concatMap unique (c ^. #uniqueValuesConstraints)+ where+ locate sel = [(path, resolved, value) | (path, _, value) <- locateSelector build lookupValue rules sel, Just resolved <- [Map.lookup sel (c ^. #resolvedSelectors)]]+ present s = any (\(_, r, v) -> case evaluateFieldValue r (Just v) of FieldPresent _ -> True; _ -> False) (locate s)+ malformed s = any (\(_, r, v) -> case evaluateFieldValue r (Just v) of FieldWrongShape _ -> True; _ -> False) (locate s)+ presence p =+ let gated = maybe True (\g -> case lookupValue (g ^. #field) of Just (String v) -> v `elem` g ^. #hasValue; _ -> False) (p ^. #when)+ triggered = null (p ^. #whenAnyPresent) || any present (p ^. #whenAnyPresent)+ in if not (gated && triggered)+ then []+ else+ [ConstraintPresenceMissing cid (build (s ^. #field)) (p ^. #when) | s <- p ^. #requireAll, not (present s), not (malformed s)]+ <> [ConstraintAlternativeMissing cid (map (build . (^. #field)) (p ^. #requireAny)) (p ^. #when) | not (null (p ^. #requireAny)), not (any present (p ^. #requireAny)), not (any malformed (p ^. #requireAny))]+ timestamp key = do+ String v <- lookupValue key+ if valueMatchesFormat Rfc3339Utc (String v) then iso8601ParseM (Text.unpack v) else Nothing+ timeOrder t = [TimestampOrderViolation cid (build (t ^. #before)) (build (t ^. #after)) | Just a <- [timestamp (t ^. #before) :: Maybe UTCTime], Just b <- [timestamp (t ^. #after)], a >= b]+ occurrences sel = concatMap project (locate sel)+ project (path, r, v) = case (r ^. #cardinality, v) of+ (List, Array vs) -> [(appendPath path (ArrayIndex i), r, x) | (i, x) <- zip [0 ..] (Vector.toList vs)]+ (Scalar, _) -> [(path, r, v)]+ _ -> [] -- ordinary cardinality check owns this failure+ usable sel = [(path, v) | (path, r, String v) <- occurrences sel, not (Text.null (Text.strip v)), null (r ^. #allowedValues) || valueMatchesVocabulary (r ^. #allowedValues) (String v), maybe True (\f -> valueMatchesFormat f (String v)) (r ^. #format)]+ bad sel =+ [ TextOperandMismatch cid path value+ | (path, r, value) <- occurrences sel,+ case value of String _ -> False; _ -> True,+ ordinaryAccepts r value+ ]+ ordinaryAccepts r v = case evaluateFieldValue (r & #cardinality .~ Scalar) (Just v) of+ FieldWrongShape _ | r ^. #cardinality /= List -> False+ _ ->+ (null (r ^. #allowedValues) || valueMatchesVocabulary (r ^. #allowedValues) v)+ && maybe True (`valueMatchesFormat` v) (r ^. #format)+ && not (isJust (r ^. #reference) || isJust (r ^. #path))+ membership m =+ bad (m ^. #source)+ <> bad (m ^. #target)+ <> [MembershipValueMissing cid path value (m ^. #target) | (path, value) <- usable (m ^. #source), value `notElem` map snd (usable (m ^. #target))]+ unique u = concatMap bad (u ^. #selectors) <> snd (List.foldl' add (Map.empty, []) (concatMap usable (u ^. #selectors)))+ add (seen, errors) (path, value) = case Map.lookup value seen of+ Nothing -> (Map.insert value path seen, errors)+ Just original -> (seen, errors <> [DuplicateSelectedValue cid path value original])++-- | Shared, deterministic prose for both declared and effective constraints.+renderConceptConstraints :: EffectiveConceptConstraints -> [Text]+renderConceptConstraints c =+ category "Presence constraints" (map presence (c ^. #presenceConstraints))+ <> category "Timestamp order constraints" [t ^. #before <> " < " <> t ^. #after | t <- c ^. #timestampOrderConstraints]+ <> category "Membership constraints (text operands)" [selector (m ^. #source) <> " in " <> selector (m ^. #target) | m <- c ^. #membershipConstraints]+ <> category "Unique values constraints (text operands)" [Text.intercalate ", " (map selector (u ^. #selectors)) | u <- c ^. #uniqueValuesConstraints]+ where+ category label [] = [label <> ": none"]+ category label values = [label <> ": " <> Text.intercalate "; " values]+ selector = renderValueSelector+ presence p =+ "when "+ <> maybe "always" (\g -> g ^. #field <> " in [" <> Text.intercalate ", " (g ^. #hasValue) <> "]") (p ^. #when)+ <> "; any present ["+ <> Text.intercalate ", " (map selector (p ^. #whenAnyPresent))+ <> "] (empty means no presence trigger)"+ <> "; require all ["+ <> Text.intercalate ", " (map selector (p ^. #requireAll))+ <> "]"+ <> "; require any ["+ <> Text.intercalate ", " (map selector (p ^. #requireAny))+ <> "]"++normalizeRecordShapes :: EffectiveFieldRule -> EffectiveFieldRule+normalizeRecordShapes r = case (r ^. #elementFields, r ^. #objectFields) of+ (Just a, Just b) ->+ let members = mergeRules a b+ constraints = resolveConstraints members (mergeConstraints (r ^. #elementConstraints) (r ^. #objectConstraints))+ in r+ & #elementFields+ .~ Just members+ & #objectFields+ .~ Just members+ & #elementConstraints+ .~ constraints+ & #objectConstraints+ .~ constraints+ _ -> r++-- | Resolved leaf rules for every selector and condition gate in this scope.+-- Keys preserve the public bounded locations; values are read-only effective rules.+conceptResolvedSelectors :: EffectiveConceptConstraints -> Map ValueSelector EffectiveFieldRule+conceptResolvedSelectors c = c ^. #resolvedSelectors++resolveConstraints :: Map Text EffectiveFieldRule -> EffectiveConceptConstraints -> EffectiveConceptConstraints+resolveConstraints rules c =+ c+ & #resolvedSelectors+ .~ Map.fromList+ [(s, r) | s <- List.nub allSelectors, Just r <- [resolveValueSelector rules s]]+ where+ sibling key = ValueSelector key Nothing+ allSelectors =+ concat [p ^. #whenAnyPresent <> p ^. #requireAll <> p ^. #requireAny <> maybe [] (\g -> [sibling (g ^. #field)]) (p ^. #when) | p <- c ^. #presenceConstraints]+ <> concat [[sibling (t ^. #before), sibling (t ^. #after)] | t <- c ^. #timestampOrderConstraints]+ <> concat [[m ^. #source, m ^. #target] | m <- c ^. #membershipConstraints]+ <> concatMap (^. #selectors) (c ^. #uniqueValuesConstraints)++-- | Display a bounded declaration location, as distinct from an indexed value path.+renderValueSelector :: ValueSelector -> Text+renderValueSelector s = s ^. #field <> maybe "" (\m -> "[]." <> m) (s ^. #member)
src/Okf/Profile/Discovery.hs view
@@ -14,7 +14,7 @@ ) where -import Control.Exception (Exception, SomeException, catch, throw)+import Control.Exception (Exception, SomeException, throw) import Control.Monad (filterM) import Data.List qualified as List import Data.Text qualified as Text@@ -24,6 +24,7 @@ import Dhall.Parser qualified import Dhall.TypeCheck qualified import Okf.Discovery (isSearchableDirectory, listDirectorySafe)+import Okf.Exception (catchSynchronous) import Okf.Prelude import Okf.Profile (ProfileSpec, decodeProfileExpr) import System.Directory (doesFileExist, pathIsSymbolicLink)@@ -105,7 +106,7 @@ -- network request. The file is parsed exactly once before import resolution. loadProfileDescriptorWithoutNetwork :: FilePath -> IO (Either Text ProfileSpec) loadProfileDescriptorWithoutNetwork path =- action `catch` \(exception :: SomeException) -> pure (Left (Text.pack (show exception)))+ action `catchSynchronous` \(exception :: SomeException) -> pure (Left (Text.pack (show exception))) where action = do contents <- Text.IO.readFile path@@ -136,4 +137,4 @@ safelyFalse :: IO Bool -> IO Bool safelyFalse action =- action `catch` \(_ :: SomeException) -> pure False+ action `catchSynchronous` \(_ :: SomeException) -> pure False
src/Okf/Profile/Documentation.hs view
@@ -283,6 +283,8 @@ "type's rules, which is the form that actually applies.", "" ]+ <> map ("- " <>) (renderConceptConstraints (compiledProfileBaseConstraints compiled))+ <> [""] <> baseRuleLines <> [ "## Concept types", ""@@ -377,6 +379,8 @@ "the profile-wide rule and this type's own rule, already merged.", "" ]+ <> map ("- " <>) (renderConceptConstraints (compiledProfileConstraintsForType compiled typeName))+ <> [""] <> group "Required" (Map.toAscList (Map.filter ((== PresenceRequired) . presenceClassOf) rules)) <> group "Recommended" (Map.toAscList (Map.filter ((== PresenceRecommended) . presenceClassOf) rules)) <> group "Optional" (Map.toAscList (Map.filter ((== PresenceOptional) . presenceClassOf) rules))@@ -431,13 +435,17 @@ "- Format: " <> maybe "none" renderFieldFormatName (fieldRuleFormat rule), "- Reference: " <> maybe "none" renderReference (fieldRuleReference rule), "- Path: " <> maybe "none" renderPathRule (fieldRulePath rule),- "- Unique by: " <> maybe "none" code (fieldRuleUniqueBy rule)+ "- Unique by: " <> maybe "none" code (fieldRuleUniqueBy rule),+ "- Object field names: " <> closureName (fieldRuleObjectAllowsUnknownFields rule),+ "- Element field names: " <> closureName (fieldRuleElementAllowsUnknownFields rule) ] <> conditionBullets <> objectFieldBullets <> elementFieldBullets <> strictNote + closureName = maybe "none" (\open -> if open then "open" else "closed")+ conditionBullets = case [clause | clause <- fieldRulePresenceClauses rule, isJust (presenceClauseCondition clause)] of [] -> ["- Condition: none"]@@ -458,6 +466,7 @@ Just members -> ["- Object fields:"] <> [" - " <> renderElementField memberKey memberRule | (memberKey, memberRule) <- Map.toAscList members]+ <> map (" - " <>) (take 2 (renderConceptConstraints (fieldRuleObjectConstraints rule))) elementFieldBullets = case fieldRuleElementFields rule of@@ -465,6 +474,7 @@ Just nested -> ["- Element fields:"] <> [" - " <> renderElementField nestedKey nestedRule | (nestedKey, nestedRule) <- Map.toAscList nested]+ <> (if isJust (fieldRuleObjectFields rule) then [] else map (" - " <>) (take 2 (renderConceptConstraints (fieldRuleElementConstraints rule)))) strictNote = case presenceClassOf rule of
src/Okf/Profile/Registry.hs view
@@ -46,7 +46,7 @@ ) where -import Control.Exception (SomeException, catch, fromException)+import Control.Exception (SomeException, fromException) import Data.List qualified as List import Data.Text qualified as Text import Data.Text.IO qualified as Text.IO@@ -58,6 +58,7 @@ import Dhall.Parser qualified as Dhall.Parser import Dhall.Src (Src) import Dhall.TypeCheck qualified as Dhall.TypeCheck+import Okf.Exception (catchSynchronous) import Okf.Prelude import Okf.Profile (ProfileSpec, decodeProfileExpr) import Okf.Profile.Discovery (loadProfileDescriptorWithoutNetwork)@@ -189,8 +190,8 @@ -- newer tag means changing the URL and the hash together. defaultRegistryReference :: Text defaultRegistryReference =- "https://raw.githubusercontent.com/shinzui/okf-profiles/v0.19.0/package.dhall\- \ sha256:85176d78369b6d73c9f13c30277903b629d6bf048a4c7d71fc26e68b99c3eaa6"+ "https://raw.githubusercontent.com/shinzui/okf-profiles/v0.25.0/package.dhall\+ \ sha256:ec74cc6f7b8ee1a5af81ee7fc8804f35cf2c9ba4010671d9e881da72eb6719d3" -- | How an entry with an empty export path is displayed. rootExportLabel :: Text@@ -295,7 +296,7 @@ Just registryError -> pure (Left registryError) Nothing -> (Right . registryEntries <$> evaluateRef reference)- `catch` \(exception :: SomeException) ->+ `catchSynchronous` \(exception :: SomeException) -> pure (Left (classifyRegistryException exception)) registryReferencePreflight :: RegistryRef -> IO (Maybe RegistryLoadError)
src/Okf/Query.hs view
@@ -80,6 +80,7 @@ import Okf.Bundle (Concept, conceptDocument) import Okf.Document (OKFDocument (frontmatter), coreFrontmatterFields, frontmatterLookup) import Okf.Prelude+ -- Imported with an explicit list that leaves out the 'Cardinality' -- constructors: two of them are named 'List' and 'Object', which would clash -- with aeson's 'Value' constructors of the same names.
test/Main.hs view
@@ -2,7 +2,7 @@ module Main (main) where -import Control.Exception (bracket)+import Control.Exception (AsyncException (UserInterrupt), bracket, throwIO, try) import Data.Aeson (object, toJSON, (.=)) import Data.Aeson qualified as Aeson import Data.Aeson.KeyMap qualified as KeyMap@@ -10,7 +10,7 @@ import Data.List qualified as List import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Maybe (catMaybes, maybeToList)+import Data.Maybe (catMaybes, mapMaybe, maybeToList) import Data.Set qualified as Set import Data.Text qualified as Text import Data.Text.IO qualified as Text.IO@@ -21,14 +21,17 @@ import Okf.ConceptId import Okf.Discovery import Okf.Document+import Okf.Exception (catchSynchronous) import Okf.Graph import Okf.Index import Okf.Log import Okf.Markdown import Okf.Path+ -- 'List' and 'Object' are 'Cardinality' constructors; aeson's same-named -- 'Value' constructors are reached as 'Aeson.Object' and friends. import Okf.Prelude hiding (List, Object, setField, (.=))+ -- 'HumanActor' is both an 'Okf.Actor' constructor and a 'FieldFormat' -- alternative. This module names the actor far more often, so the format is -- reached as 'Profile.HumanActor'.@@ -60,7 +63,8 @@ main = do results <- sequence- [ testIO "bootstrap descriptors round-trip from local sources and physical paths" testBootstrapRoundTrip,+ [ testIO "synchronous catches preserve asynchronous cancellation" testCatchSynchronous,+ testIO "bootstrap descriptors round-trip from local sources and physical paths" testBootstrapRoundTrip, testIO "bootstrap expressions preserve hashes and reject relative imports" testBootstrapExpressions, test "bootstrap paths and versions" testBootstrapPure, test "parseActor classifies the three specification section 7 shapes" testParseActorShapes,@@ -174,6 +178,10 @@ testIO "loadProfileFile and registry decoding preserve the complete 0.8.0.0 descriptor schema" testLoadPreGuidanceCompatibilityFixture, testIO "loadProfileFile and JSON preserve multiline guidance" testLoadGuidanceFixture, testIO "guidance does not affect profile validation" testGuidanceDoesNotAffectValidation,+ testIO "nested closure loads through current and frozen public decoders" testNestedClosureDecoding,+ test "nested closure validates records and merges policies" testNestedClosureValidation,+ test "nested closure scope, shape, condition and malformed-value matrix" testNestedClosureMatrix,+ testIO "product discovery constraints preserve scope, types and occurrence paths" testProductDiscoveryConstraints, testIO "every frozen generation fixture compiles, not merely decodes" testFrozenFixturesCompile, testIO "loadProfileFile preserves the frozen pre-bundle-version schema" testLoadPreBundleVersionCompatibilityFixture, testIO "loadProfileFile preserves the frozen pre-path schema" testLoadPrePathCompatibilityFixture,@@ -2521,7 +2529,7 @@ acceptanceCriteria <- lookupRawRule "acceptanceCriteria" (spec ^. #frontmatter . #required) assertEqual (Just "id") (acceptanceCriteria ^. #uniqueBy) case dependencies ^. #elementFields of- Just NestedRules {required = [nestedReferenceRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [nestedReferenceRule]} -> do let expectedPolicy = HandleReferenceRule "IR"@@ -2638,7 +2646,7 @@ [rule] -> do assertEqual Nothing (rule ^. #when) case rule ^. #elementFields of- Just NestedRules {required = [nestedRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [nestedRule]} -> do assertEqual "kind" (nestedRule ^. #field) assertEqual Nothing (nestedRule ^. #when) _ -> Left "expected the frozen nested rule to survive the compatibility upgrade"@@ -2668,7 +2676,7 @@ assertEqual (Just (FieldCondition "status" ["superseded"])) (targetRule ^. #when) assertEqual Nothing (targetRule ^. #reference) case reviewsRule ^. #elementFields of- Just NestedRules {required = [_kindRule, providerRule]} ->+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [_kindRule, providerRule]} -> assertEqual (Just (FieldCondition "kind" ["model"])) (providerRule ^. #when) _ -> Left "expected frozen nested conditions to survive the compatibility upgrade" _ -> Left "expected three frozen condition-aware top-level rules"@@ -2693,7 +2701,7 @@ (referenceRule ^. #reference) assertEqual (Just (FieldCondition "status" ["superseded"])) (conditionRule ^. #when) case reviewsRule ^. #elementFields of- Just NestedRules {required = [kindRule], recommended = [notesRule], optional = nestedOptional} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [kindRule], recommended = [notesRule], optional = nestedOptional} -> do assertEqual "kind" (kindRule ^. #field) assertEqual "notes" (notesRule ^. #field) assertEqual [] (map (^. #field) nestedOptional)@@ -2759,6 +2767,8 @@ "pre-bundle-version.dhall", "pre-nested-references-and-uniqueness-0.7.0.0.dhall", "pre-guidance-0.8.0.0.dhall",+ "pre-product-discovery-0.10.0.0.dhall",+ "pre-nested-closure.dhall", -- Not a frozen generation but a *documented* one: this is the descriptor -- @docs\/user\/profiles.md@ shows for the specification §10 contract as a -- house convention. It is listed here so the documented descriptor cannot@@ -2799,7 +2809,7 @@ (refRule ^. #reference) _ -> Left "expected the frozen nested reference rule to survive" case generatedRule ^. #objectFields of- Just NestedRules {required = [_byRule, atRule]} ->+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [_byRule, atRule]} -> assertEqual (Just Rfc3339Utc) (atRule ^. #format) _ -> Left "expected the frozen object rules to survive" _ -> Left "expected three frozen required rules"@@ -2971,13 +2981,13 @@ case spec ^. #frontmatter . #required of [_typeRule, sourcesRule, generatedRule] -> do case sourcesRule ^. #elementFields of- Just NestedRules {required = [resourceRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [resourceRule]} -> do assertEqual "resource" (resourceRule ^. #field) assertEqual Scalar (resourceRule ^. #cardinality) assertEqual Nothing (resourceRule ^. #path) _ -> Left "expected the frozen element-field rule to survive" case generatedRule ^. #objectFields of- Just NestedRules {required = [byRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [byRule]} -> do assertEqual "by" (byRule ^. #field) assertEqual (Just Profile.Actor) (byRule ^. #format) assertEqual Nothing (byRule ^. #path)@@ -3014,7 +3024,7 @@ case spec ^. #frontmatter . #required of [_typeRule, _titleRule, generatedRule] -> case generatedRule ^. #objectFields of- Just NestedRules {required = [byRule, atRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [byRule, atRule]} -> do assertEqual "by" (byRule ^. #field) assertEqual Nothing (byRule ^. #format) assertEqual (Just Rfc3339Utc) (atRule ^. #format)@@ -3048,7 +3058,7 @@ (Just (handleReferenceRule "ADR" ["mori"] False)) (referenceRule ^. #reference) case reviewsRule ^. #elementFields of- Just NestedRules {required = [kindRule], recommended = [notesRule], optional = [urlRule]} -> do+ Just NestedRules {presenceConstraints = [], timestampOrderConstraints = [], required = [kindRule], recommended = [notesRule], optional = [urlRule]} -> do assertEqual "kind" (kindRule ^. #field) assertEqual "notes" (notesRule ^. #field) assertEqual "url" (urlRule ^. #field)@@ -3131,7 +3141,11 @@ "idField" .= ("docId" :: Text), "frontmatter" .= object- [ "required"+ [ "presenceConstraints" .= ([] :: [PresenceConstraint]),+ "timestampOrderConstraints" .= ([] :: [TimestampOrderConstraint]),+ "membershipConstraints" .= ([] :: [MembershipConstraint]),+ "uniqueValuesConstraints" .= ([] :: [UniqueValuesConstraint]),+ "required" .= [ object [ "field" .= ("type" :: Text), "description"@@ -3201,7 +3215,11 @@ "guidance" .= (Nothing :: Maybe Text), "frontmatter" .= object- [ "required" .= ([] :: [FieldRule]),+ [ "presenceConstraints" .= ([] :: [PresenceConstraint]),+ "timestampOrderConstraints" .= ([] :: [TimestampOrderConstraint]),+ "membershipConstraints" .= ([] :: [MembershipConstraint]),+ "uniqueValuesConstraints" .= ([] :: [UniqueValuesConstraint]),+ "required" .= ([] :: [FieldRule]), "recommended" .= ([] :: [FieldRule]), "optional" .= ([] :: [FieldRule]) ],@@ -3283,17 +3301,30 @@ Left err -> Left ("failed to load pinned catalogue snapshot: " <> err) Right entries -> do assertEqual- [ "assurance.failureModes",+ [ "assurance.claimChecks",+ "assurance.failureModes", "assurance.reviews", "assurance.verificationEvidence",+ "coordination.audiences", "coordination.bugReports",+ "coordination.campaigns", "coordination.capabilities",+ "coordination.designs",+ "coordination.features",+ "coordination.frictionLog", "coordination.improvementRequests",+ "coordination.jobs",+ "coordination.launches", "coordination.patternApplications",+ "coordination.strategy",+ "coordination.transitions", "coordination.useCases", "documentation.architectureDecisions",+ "documentation.messaging", "documentation.patternCatalog",+ "documentation.productResearch", "documentation.researchDocuments",+ "documentation.runbooks", "documentation.specifications", "documentation.terminology", "documentation.userDocumentation",@@ -3727,7 +3758,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [requiredField "type", requiredField "title"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type", requiredField "title"], recommended = [], optional = [] },@@ -3759,7 +3794,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [requiredField "type", requiredField "title"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type", requiredField "title"], recommended = [], optional = [] },@@ -3783,7 +3822,7 @@ } emptyTestFrontmatterRules :: FrontmatterRules-emptyTestFrontmatterRules = FrontmatterRules {required = [], recommended = [], optional = []}+emptyTestFrontmatterRules = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [], optional = []} typeAwareProfileSpec :: ProfileSpec typeAwareProfileSpec =@@ -3794,7 +3833,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "title" (Just "Global title.") [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "title" (Just "Global title.") [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "owner" (Just "Profile-level owner.") [] Any Nothing Nothing Nothing Nothing], optional = [] },@@ -3809,7 +3852,11 @@ guidance = Nothing, frontmatter = FrontmatterRules- { required = [fieldRule "owner" (Just "Responsible person.") [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "owner" (Just "Responsible person.") [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "reviewer" (Just "Second pair of eyes.") [] Any Nothing Nothing Nothing Nothing, fieldRule "title" (Just "Type title.") [] Any Nothing Nothing Nothing Nothing], optional = [] },@@ -3829,7 +3876,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [duplicateField, duplicateField],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [duplicateField, duplicateField], recommended = [duplicateField], optional = [] },@@ -3862,14 +3913,22 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "status" Nothing ["draft", "approved", "approved"] Any Nothing Nothing Nothing Nothing], optional = [] }, types = [ withTypeFrontmatter FrontmatterRules- { required = [fieldRule "status" Nothing ["approved", "archived"] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "status" Nothing ["approved", "archived"] Any Nothing Nothing Nothing Nothing], recommended = [], optional = [] }@@ -3896,7 +3955,11 @@ { types = [ withTypeFrontmatter FrontmatterRules- { required = [fieldRule "status" Nothing ["closed"] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "status" Nothing ["closed"] Any Nothing Nothing Nothing Nothing], recommended = [], optional = [] }@@ -3911,13 +3974,21 @@ testCompiledCardinality = do let profileRules = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "status" Nothing [] Scalar Nothing Nothing Nothing Nothing], optional = [] } typeRules cardinality = FrontmatterRules- { required = [fieldRule "status" Nothing [] cardinality Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "status" Nothing [] cardinality Nothing Nothing Nothing Nothing], recommended = [], optional = [] }@@ -3946,7 +4017,11 @@ vocabularyProfileSpec { frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "status" Nothing ["draft", "approved"] Any Nothing Nothing Nothing Nothing], optional = [] },@@ -4025,14 +4100,22 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "homepage" Nothing [] Any (Just profileFormat) Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "homepage" Nothing [] Any (Just profileFormat) Nothing Nothing Nothing], recommended = [], optional = [] }, types = [ withTypeFrontmatter FrontmatterRules- { required = [fieldRule "homepage" Nothing [] Any (Just typeFormat) Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "homepage" Nothing [] Any (Just typeFormat) Nothing Nothing Nothing], recommended = [], optional = [] }@@ -4057,7 +4140,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "handle" Nothing [] Any (Just (DocumentHandle "1ADR")) Nothing Nothing Nothing, fieldRule "source" Nothing [] Any (Just (UriWithScheme "https_")) Nothing Nothing Nothing@@ -4180,7 +4267,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "usage_count" Nothing [] cardinality (Just fieldFormat) Nothing Nothing Nothing ],@@ -4227,7 +4318,11 @@ okfVersion = "0.2", frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing, fieldRule "value" Nothing [] Any (Just profileFormat) Nothing Nothing Nothing ],@@ -4237,7 +4332,11 @@ types = [ withTypeFrontmatter FrontmatterRules- { required = [fieldRule "value" Nothing [] Any (Just typeFormat) Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "value" Nothing [] Any (Just typeFormat) Nothing Nothing Nothing], recommended = [], optional = [] }@@ -4273,7 +4372,11 @@ { okfVersion = "0.2", frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing], recommended = [fieldRule "value" Nothing [] cardinality (Just fieldFormat) Nothing Nothing Nothing], optional = [] },@@ -4286,7 +4389,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing] <> [rule | isRequired],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing] <> [rule | isRequired], recommended = [rule | not isRequired], optional = [] },@@ -4300,13 +4407,19 @@ testCompiledNestedRules = do let profileRules = NestedRules- { required = [nestedFieldRule "kind" Nothing ["decision", "implementation"] Any Nothing Nothing Nothing],+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [nestedFieldRule "kind" Nothing ["decision", "implementation"] Any Nothing Nothing Nothing], recommended = [nestedFieldRule "notes" Nothing [] Scalar Nothing Nothing Nothing], optional = [] } typeRules = NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ nestedFieldRule "kind" Nothing ["implementation", "operations"] Any Nothing Nothing Nothing, nestedFieldRule "outcome" Nothing ["approved", "rejected"] Any Nothing Nothing Nothing ],@@ -4401,7 +4514,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ requiredField "type", fieldRule "reviews" Nothing [] outerCardinality Nothing (Just profileNested) Nothing Nothing ],@@ -4419,7 +4536,11 @@ guidance = Nothing, frontmatter = FrontmatterRules- { required = maybe [] (\rules -> [fieldRule "reviews" Nothing [] Any Nothing (Just rules) Nothing Nothing]) typeNested,+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = maybe [] (\rules -> [fieldRule "reviews" Nothing [] Any Nothing (Just rules) Nothing Nothing]) typeNested, recommended = [], optional = [] },@@ -4438,7 +4559,10 @@ where nestedRules = NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ nestedFieldRule "kind" Nothing ["human", "model"] Any Nothing Nothing Nothing, nestedFieldRule "reviewer" Nothing [] Scalar Nothing Nothing Nothing, nestedFieldRule "reviewed_at" Nothing [] Any (Just Rfc3339Utc) Nothing Nothing,@@ -4468,7 +4592,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ requiredField "type", FieldRule { field = key,@@ -4499,7 +4627,10 @@ provenanceMemberRules :: NestedRules provenanceMemberRules = NestedRules- { required = [nestedFieldRule "by" (Just "Who or what produced this content.") [] Any Nothing Nothing Nothing],+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [nestedFieldRule "by" (Just "Who or what produced this content.") [] Any Nothing Nothing Nothing], recommended = [nestedFieldRule "at" Nothing [] Any (Just Rfc3339Utc) Nothing Nothing], optional = [] }@@ -4566,7 +4697,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [requiredField "type"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type"], recommended = [], -- Optional rather than required, so a concept that simply does not -- carry the key reports nothing and the tests below see only the@@ -4591,7 +4726,11 @@ guidance = Nothing, frontmatter = FrontmatterRules- { required = [],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [], recommended = [], optional = [(requiredField "resource") {path = typePath}] },@@ -4617,7 +4756,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [requiredField "type"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type"], recommended = [], -- Optional for the same reason 'pathProfileWith' is: the tests are -- about what a present value resolves to, not about presence.@@ -4632,7 +4775,10 @@ where memberRules = NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ (nestedFieldRule "resource" Nothing [] Any Nothing Nothing Nothing) { path = Just (PathReferenceRule permittedSchemes False) }@@ -4917,7 +5063,10 @@ Any ( Just NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ (nestedFieldRule "resource" Nothing [] Any Nothing Nothing Nothing) { path = Just (PathReferenceRule [] False) }@@ -4954,7 +5103,11 @@ okfVersion = declaredVersion, frontmatter = FrontmatterRules- { required = [requiredField "type"] <> [rule | rule <- rules, listName == "required"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type"] <> [rule | rule <- rules, listName == "required"], recommended = [rule | rule <- rules, listName == "recommended"], optional = [rule | rule <- rules, listName == "optional"] },@@ -5129,7 +5282,7 @@ compileWith rules = compileProfile typeAwareProfileSpec- { frontmatter = FrontmatterRules {required = rules, recommended = [], optional = []},+ { frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = rules, recommended = [], optional = []}, allowUnknownTypes = True, types = [] }@@ -5154,7 +5307,10 @@ (compileWith [fieldRule "status" Nothing ["active"] Scalar Nothing Nothing Nothing (Just (FieldCondition "status" ["active"]))]) let nestedCrossScope = NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ nestedFieldRule "kind" Nothing ["human", "model"] Scalar Nothing Nothing Nothing, nestedFieldRule "provider" Nothing [] Scalar Nothing Nothing (Just (FieldCondition "status" ["active"])) ],@@ -5165,7 +5321,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required =+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ source "status" ["active"] Scalar, fieldRule "reviews" Nothing [] List Nothing (Just nestedCrossScope) Nothing Nothing ],@@ -5191,11 +5351,11 @@ fieldRule "supersededBy" Nothing ["ADR-1"] Scalar Nothing Nothing Nothing (Just (FieldCondition "status" ["superseded"])) base = typeAwareProfileSpec- { frontmatter = FrontmatterRules {required = [requiredField "type", statusRule], recommended = [recommendedTarget], optional = []},+ { frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "type", statusRule], recommended = [recommendedTarget], optional = []}, allowUnknownTypes = True, types = [ withTypeFrontmatter- FrontmatterRules {required = [requiredTarget], recommended = [], optional = []}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredTarget], recommended = [], optional = []} (firstTypeRule typeAwareProfileSpec) ] }@@ -5236,7 +5396,10 @@ testNestedConditionalPresence = do let nestedRules = NestedRules- { required =+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [ nestedFieldRule "kind" Nothing ["human", "model"] Scalar Nothing Nothing Nothing, nestedFieldRule "provider" Nothing [] Scalar Nothing Nothing (Just (FieldCondition "kind" ["model"])) ],@@ -5279,7 +5442,7 @@ baseType = firstTypeRule testDocumentIdProfileSpec specWith profileIdField typeRules profileRules = testDocumentIdProfileSpec- { frontmatter = FrontmatterRules {required = profileRules, recommended = [], optional = []},+ { frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = profileRules, recommended = [], optional = []}, idField = profileIdField, requireBundleVersion = Nothing, types = typeRules@@ -5293,7 +5456,7 @@ formatSpec = specWith (Just "docId") [baseType] [referenceRule "supersedes" "ADR" [] (Just (DocumentHandle "ADR"))] typeReference = referenceRule "supersedes" "RFC" [] Nothing conflictingType :: TypeRule- conflictingType = baseType & #frontmatter .~ FrontmatterRules {required = [typeReference], recommended = [], optional = []}+ conflictingType = baseType & #frontmatter .~ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [typeReference], recommended = [], optional = []} rfcType = baseType {type_ = "RFC", idPrefix = Just "RFC", pathPattern = Nothing} conflictSpec = specWith (Just "docId") [conflictingType, rfcType] [referenceRule "supersedes" "ADR" [] Nothing] assertEqual@@ -5455,7 +5618,7 @@ spec { types = [ typeRule- { frontmatter = FrontmatterRules {required = [typeField], recommended = [], optional = []}+ { frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [typeField], recommended = [], optional = []} } | typeRule <- spec ^. #types ]@@ -5533,7 +5696,11 @@ testDocumentIdProfileSpec & #frontmatter .~ FrontmatterRules- { required = [requiredField "type", requiredField "title"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type", requiredField "title"], recommended = referenceRules, optional = [] }@@ -5596,7 +5763,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [requiredField "type"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type"], recommended = [requiredField "owner"], optional = optionalRules },@@ -5658,7 +5829,11 @@ testDocumentIdProfileSpec & #frontmatter .~ FrontmatterRules- { required = [requiredField "type", requiredField "title"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type", requiredField "title"], recommended = [], optional = [fieldRule "supersedes" Nothing [] Scalar Nothing Nothing (Just (handleReferenceRule "ADR" [] False)) Nothing] }@@ -5679,7 +5854,10 @@ testOptionalNestedFieldPresence = do let nestedRules = NestedRules- { required = [nestedFieldRule "kind" Nothing ["human", "model"] Scalar Nothing Nothing Nothing],+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [nestedFieldRule "kind" Nothing ["human", "model"] Scalar Nothing Nothing Nothing], recommended = [nestedFieldRule "notes" Nothing [] Scalar Nothing Nothing Nothing], optional = [nestedFieldRule "model" Nothing ["opus", "sonnet"] Scalar Nothing Nothing Nothing] }@@ -5711,7 +5889,11 @@ typeAwareProfileSpec { frontmatter = FrontmatterRules- { required = [requiredField "type"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [requiredField "type"], recommended = [], optional = [requiredField "supersedes"] },@@ -5742,12 +5924,12 @@ } recommendedThenOptional = specWith- FrontmatterRules {required = [requiredField "type"], recommended = [ownerRule], optional = []}- FrontmatterRules {required = [], recommended = [], optional = [ownerRule]}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "type"], recommended = [ownerRule], optional = []}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [], optional = [ownerRule]} optionalThenRecommended = specWith- FrontmatterRules {required = [requiredField "type"], recommended = [], optional = [ownerRule]}- FrontmatterRules {required = [], recommended = [ownerRule], optional = []}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "type"], recommended = [], optional = [ownerRule]}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [ownerRule], optional = []} concept <- profileConcept "owned/one" [("type", String "Owned Concept")] "# One\n" cid <- parseTestConceptId "owned/one" for_ [recommendedThenOptional, optionalThenRecommended] $ \spec -> do@@ -5770,21 +5952,24 @@ statusRule = fieldRule "status" Nothing ["active"] Scalar Nothing Nothing Nothing Nothing assertEqual (Left (ConflictingFieldRequirement Nothing "owner" :| []))- (compileProfile (specWith FrontmatterRules {required = [key "type", key "owner"], recommended = [], optional = [key "owner"]}))+ (compileProfile (specWith FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [key "type", key "owner"], recommended = [], optional = [key "owner"]})) assertEqual (Left (ConflictingFieldRequirement Nothing "owner" :| []))- (compileProfile (specWith FrontmatterRules {required = [key "type"], recommended = [key "owner"], optional = [key "owner"]}))+ (compileProfile (specWith FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [key "type"], recommended = [key "owner"], optional = [key "owner"]})) assertEqual (Left (DuplicateFieldRule Nothing "optional" "owner" :| []))- (compileProfile (specWith FrontmatterRules {required = [key "type"], recommended = [], optional = [key "owner", key "owner"]}))+ (compileProfile (specWith FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [key "type"], recommended = [], optional = [key "owner", key "owner"]})) assertEqual (Left (OptionalFieldWithCondition Nothing (fieldPath "supersededBy") :| [])) ( compileProfile- (specWith FrontmatterRules {required = [key "type", statusRule], recommended = [], optional = [conditioned "supersededBy" "status"]})+ (specWith FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [key "type", statusRule], recommended = [], optional = [conditioned "supersededBy" "status"]}) ) let nestedRules = NestedRules- { required = [nestedFieldRule "kind" Nothing ["model"] Scalar Nothing Nothing Nothing],+ { allowUnknownFields = True,+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [nestedFieldRule "kind" Nothing ["model"] Scalar Nothing Nothing Nothing], recommended = [], optional = [nestedFieldRule "model" Nothing [] Scalar Nothing Nothing (Just (FieldCondition "kind" ["model"]))] }@@ -5794,7 +5979,11 @@ let optionalParent = specWith FrontmatterRules- { required = [key "type"],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [key "type"], recommended = [], optional = [fieldRule "reviews" Nothing [] List Nothing (Just nestedRules) Nothing Nothing] }@@ -5865,15 +6054,15 @@ let ownedRule :: TypeRule ownedRule = withTypeFrontmatter- FrontmatterRules {required = [requiredField "owner"], recommended = [], optional = []}+ FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "owner"], recommended = [], optional = []} (firstTypeRule typeAwareProfileSpec) reviewRule = withTypeName "Review"- (withTypeFrontmatter FrontmatterRules {required = [requiredField "reviewer"], recommended = [], optional = []} ownedRule)+ (withTypeFrontmatter FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "reviewer"], recommended = [], optional = []} ownedRule) closed = typeAwareProfileSpec- { frontmatter = FrontmatterRules {required = [requiredField "type", requiredField "status"], recommended = [], optional = []},+ { frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [requiredField "type", requiredField "status"], recommended = [], optional = []}, allowUnknownFields = False, idField = Just "requestId", requireBundleVersion = Nothing,@@ -5902,7 +6091,7 @@ FieldNotInProfile cid "stauts" ] (validateProfile PermissiveConformance compiled [typo])- let reopened = closed {allowUnknownFields = True}+ let reopened = closed & #allowUnknownFields .~ True reopenedCompiled <- firstShow (compileProfile reopened) assertEqual [MissingProfileField cid "status" Nothing]@@ -6038,7 +6227,7 @@ ( objectProfileWithRules "generated" Any- (Just (NestedRules {required = [], recommended = [], optional = []}))+ (Just (NestedRules {allowUnknownFields = True, presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [], optional = []})) Nothing ) )@@ -6505,7 +6694,7 @@ { type_ = typeName, description = Nothing, guidance = Nothing,- frontmatter = FrontmatterRules {required = [], recommended = [], optional = []},+ frontmatter = FrontmatterRules {membershipConstraints = [], uniqueValuesConstraints = [], presenceConstraints = [], timestampOrderConstraints = [], required = [], recommended = [], optional = []}, pathPattern = Nothing, resourceScheme = Nothing, requireSchemaSection = False,@@ -6525,7 +6714,11 @@ okfVersion = "0.1", frontmatter = FrontmatterRules- { required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing],+ { membershipConstraints = [],+ uniqueValuesConstraints = [],+ presenceConstraints = [],+ timestampOrderConstraints = [],+ required = [fieldRule "type" Nothing [] Any Nothing Nothing Nothing Nothing], recommended = [], optional = [] },@@ -7993,3 +8186,394 @@ assertEqual (parseOkfVersion "0.2") (bootstrapOkfVersion Nothing spec) assertEqual (parseOkfVersion "0.3") (bootstrapOkfVersion (parseOkfVersion "0.3") spec) assertEqual (parseOkfVersion "0.1") (bootstrapOkfVersion Nothing (spec & #requireBundleVersion .~ Just "bad"))++testCatchSynchronous :: IO (Either Text ())+testCatchSynchronous = do+ synchronous <- catchSynchronous (ioError (userError "expected IO failure")) (\_ -> pure True)+ asynchronous <- try @AsyncException (catchSynchronous (throwIO UserInterrupt) (\_ -> pure False))+ pure $ if synchronous && asynchronous == Left UserInterrupt then Right () else Left "asynchronous cancellation was swallowed"++-- Regression matrix for bounded same-concept constraints. The descriptor is+-- loaded through both public decoders; mutations are independent concepts.+testProductDiscoveryConstraints :: IO (Either Text ())+testProductDiscoveryConstraints = do+ path <- fixtureFilePath "profiles/product-discovery.dhall"+ oldPath <- fixtureFilePath "profiles/pre-product-discovery-0.10.0.0.dhall"+ loaded <- loadProfileFile path+ registry <- loadRegistry (RegistryFile path)+ registryPath <- fixtureFilePath "profiles/product-discovery-registry.dhall"+ namedRegistry <- loadRegistry (RegistryFile registryPath)+ old <- loadProfileFile oldPath+ oldRegistry <- loadRegistry (RegistryFile oldPath)+ malformedPath <- fixtureFilePath "profiles/product-discovery-malformed.dhall"+ malformed <- loadRegistry (RegistryFile malformedPath)+ pure $ do+ spec <- loaded+ malformedEntries <- firstShow malformed+ assertEqual [] malformedEntries+ compiled <- firstShow (compileProfile spec)+ named <- firstShow namedRegistry+ assertEqual ["productDiscovery"] (map (^. #export) named)+ assertEqual [spec] (map (^. #spec) named)+ entries <- firstShow registry+ assertEqual [spec] (map (^. #spec) entries)+ oldSpec <- old+ _ <- firstShow (compileProfile oldSpec)+ oldEntries <- firstShow oldRegistry+ assertEqual [oldSpec] (map (^. #spec) oldEntries)+ assertEqual (Just "Frozen guidance") (oldSpec ^. #guidance)+ let arr = toJSON :: [Value] -> Value+ rec = object+ outcome fields = ("outcomes", arr [rec fields])+ complete = [("metric", String "duration"), ("unit", String "s"), ("direction", String "down"), ("method", String "observe")]+ check fields = do+ concept <- profileConcept "one" (("type", String "Job") : fields) ""+ pure (validateProfile PermissiveConformance compiled [concept])+ passes fields = check fields >>= assertEqual []+ fails fields = do+ violations <- check fields+ if null violations then Left ("expected rejection: " <> Text.pack (show fields)) else Right ()+ hasPath expected violations =+ if any (Text.isInfixOf expected . Text.pack . show) violations then Right () else Left ("missing path " <> expected <> ": " <> Text.pack (show violations))+ passes [outcome [("name", String "qualitative")]]+ passes [outcome complete]+ for_ ["metric", "unit", "direction", "method", "baseline", "target", "population", "period"] $ \key ->+ fails [outcome [(key, if key `elem` ["baseline", "target"] then Number 0 else String "usable")]]+ let measured = complete <> [("baseline", Number 0), ("population", String "authors"), ("period", String "week")]+ fails [outcome measured]+ passes [outcome measured, ("sources", arr [String "interview"])]+ passes [outcome (complete <> [("target", Number 2)])]+ for_ [Number 0, Number (-2), Number 1.25, Number 1e400] $ \n -> passes [outcome (complete <> [("target", n)])]+ for_ [Bool True, String "1.25", Null] $ \n -> fails [outcome (complete <> [("target", n)])]+ passes [("outcomeRefs", arr [rec [("outcomeNames", arr [])]])]+ fails [("outcomeRefs", arr [rec [("outcomeNames", arr [String "faster"])]])]+ for_ ["planned", "in-progress", "delivered"] $ \status -> do+ fails [("status", String status)]+ passes [("status", String status), ("features", arr [rec []])]+ passes [("status", String status), ("featureRefs", arr [String "f"])]+ fails [("status", String status), ("features", arr []), ("featureRefs", arr [])]+ fails [("status", String status), ("features", arr [Number 1])]+ passes [("status", String "draft")]+ let period a b = outcome [("periodStart", String a), ("periodEnd", String b)]+ passes [period "2026-10-01T00:00:00.1Z" "2026-10-01T00:00:00.2Z"]+ fails [period "2026-10-01T00:00:00Z" "2026-10-01T00:00:00Z"]+ fails [period "2026-10-02T00:00:00Z" "2026-10-01T00:00:00Z"]+ passes [outcome [("periodStart", String "2026-10-02T00:00:00Z")]]+ passes [("outcomes", arr [rec [("periodStart", String "2026-10-02T00:00:00Z")], rec [("periodEnd", String "2026-10-01T00:00:00Z")]])]+ fails [period "bad" "2026-10-01T00:00:00Z"]+ passes [("jobs", arr [rec [("name", String "author")]]), ("features", arr [rec [("jobs", arr [String "author"])]])]+ fails [("features", arr [rec [("jobs", arr [String "unknown"])]])]+ for_ [Number 42, Bool True, Null, rec [], arr []] $ \bad -> do+ fails [("jobs", arr [rec [("name", bad)]])]+ vs <- check [("features", arr [rec [("jobs", arr [bad])]])]+ hasPath "ArrayIndex 0" vs+ fails [("jobRefs", arr [bad])]+ fails [("featureRefs", arr [bad])]+ fails [("features", arr [rec [("featureRef", bad)]])]+ vs <- check [("jobs", arr [rec [("name", Number 42)]]), ("features", arr [rec [("jobs", arr [String "unknown", Number 7])]])]+ assertEqual 3 (length vs)+ fails [("jobs", arr [rec [("name", String " ")]])]+ for_ [[("featureRefs", arr [String "f", String "f"])], [("features", arr [rec [("featureRef", String "f")], rec [("featureRef", String "f")]])], [("featureRefs", arr [String "f"]), ("features", arr [rec [("featureRef", String "f")]])]] fails+ one <- profileConcept "one" [("featureRefs", arr [String "f"])] ""+ two <- profileConcept "two" [("featureRefs", arr [String "f"])] ""+ assertEqual [] (validateProfile PermissiveConformance compiled [one, two])+ let select key = ValueSelector key Nothing+ root = spec ^. #frontmatter+ invalid rootRules = case compileProfile (spec & #frontmatter .~ rootRules) of+ Left _ -> Right ()+ Right _ -> Left "expected constraint definition failure"+ pc = PresenceConstraint Nothing [] [] []+ invalid (root & #presenceConstraints .~ [pc])+ invalid (root & #presenceConstraints .~ [pc & #requireAll .~ [select "unknown"]])+ invalid (root & #presenceConstraints .~ [pc & #requireAll .~ [ValueSelector "outcomes" (Just "baseline")]])+ invalid (root & #membershipConstraints .~ [MembershipConstraint (select "jobRefs") (select "jobRefs")])+ invalid (root & #membershipConstraints .~ [MembershipConstraint (ValueSelector "outcomes" (Just "baseline")) (select "jobRefs")])+ invalid (root & #uniqueValuesConstraints .~ [UniqueValuesConstraint [select "featureRefs", select "featureRefs"]])+ invalid (root & #timestampOrderConstraints .~ [TimestampOrderConstraint "status" "status"])+ invalid (root & #presenceConstraints .~ [PresenceConstraint (Just (FieldCondition "jobRefs" ["x"])) [] [select "sources"] []])+ let baseConstraints = compiledProfileBaseConstraints compiled+ assertEqual baseConstraints (compiledProfileConstraintsForType compiled "unknown")+ assertEqual 2 (length (conceptPresenceConstraints baseConstraints))+ let withoutRelations = spec & #frontmatter . #membershipConstraints .~ [] & #frontmatter . #uniqueValuesConstraints .~ []+ loose <- firstShow (compileProfile withoutRelations)+ numeric <- profileConcept "numeric" [("jobs", arr [rec [("name", Number 42)]])] ""+ assertEqual [] (validateProfile PermissiveConformance loose [numeric])+ -- Merge conjunction, exact deduplication, and nested constraints shared by+ -- the two accepted record spellings.+ let sourceRule = fieldRule "sources" Nothing [] List Nothing Nothing Nothing Nothing+ objectMembers = NestedRules True [] [] [nestedFieldRule "value" Nothing [] Scalar Nothing Nothing Nothing] [] []+ obj = (fieldRule "context" Nothing [] Any Nothing Nothing Nothing Nothing) & #objectFields .~ Just objectMembers+ p = PresenceConstraint Nothing [ValueSelector "outcomes" (Just "name")] [select "context"] []+ objectSpec = spec & #frontmatter . #optional %~ (obj :) & #frontmatter . #presenceConstraints .~ [p]+ objectCompiled <- firstShow (compileProfile objectSpec)+ let objectCheck value = do+ concept <- profileConcept "one" [outcome [("name", String "x")], ("context", value)] ""+ pure (validateProfile PermissiveConformance objectCompiled [concept])+ objectCheck (rec [("value", String "x")]) >>= assertEqual []+ emptyObject <- objectCheck (rec [])+ assertEqual 1 (length emptyObject)+ let rootConstraint = PresenceConstraint Nothing [select "jobRefs"] [select "sources"] []+ rootOnly = emptyTestFrontmatterRules & #optional .~ [sourceRule, fieldRule "jobRefs" Nothing [] List Nothing Nothing Nothing Nothing] & #presenceConstraints .~ [rootConstraint]+ typeRule = head (testProfileSpec ^. #types) & #frontmatter .~ rootOnly & #pathPattern .~ Nothing & #resourceScheme .~ Nothing & #requireSchemaSection .~ False & #idPrefix .~ Nothing+ mergedSpec = spec & #frontmatter .~ rootOnly & #types .~ [typeRule]+ merged <- firstShow (compileProfile mergedSpec)+ assertEqual [rootConstraint] (conceptPresenceConstraints (compiledProfileConstraintsForType merged (typeRule ^. #type_)))+ let selectedRule = fieldRule "items" Nothing [] List Nothing Nothing Nothing Nothing+ dedupRoot =+ emptyTestFrontmatterRules+ & #optional+ .~ [selectedRule, sourceRule, fieldRule "jobRefs" Nothing [] List Nothing Nothing Nothing Nothing]+ & #membershipConstraints+ .~ [MembershipConstraint (select "items") (select "sources"), MembershipConstraint (select "items") (select "jobRefs")]+ dedupCompiled <- firstShow (compileProfile (spec & #frontmatter .~ dedupRoot))+ badConcept <- profileConcept "bad" [("items", arr [Number 1, Null, rec [], arr []])] ""+ assertEqual 4 (length (validateProfile PermissiveConformance dedupCompiled [badConcept]))+ -- Projected list presence observes the list once; sibling requirements stay+ -- correlated with their own record rather than borrowing from another.+ let projected = PresenceConstraint Nothing [ValueSelector "features" (Just "jobs")] [select "sources"] []+ projectedCompiled <- firstShow (compileProfile (spec & #frontmatter . #presenceConstraints .~ [projected]))+ projectedConcept <- profileConcept "projected" [("features", arr [rec [("jobs", arr [String "author"])]])] ""+ let projectedViolations = validateProfile PermissiveConformance projectedCompiled [projectedConcept]+ if any (\case ConstraintPresenceMissing {} -> True; _ -> False) projectedViolations then Right () else Left "projected whole-list trigger was lost"+ correlated <- check [("outcomeRefs", arr [rec [("outcomeNames", arr [String "faster"])], rec [("jobRef", String "JOB-1")]]), ("jobRefs", arr [String "JOB-1"])]+ assertEqual 1 (length correlated)+ let timestamps = [fieldRule key Nothing [] Scalar (Just Rfc3339Utc) Nothing Nothing Nothing | key <- ["start", "end"]]+ times = emptyTestFrontmatterRules & #optional .~ timestamps & #timestampOrderConstraints .~ [TimestampOrderConstraint "start" "end"]+ rootTimes <- firstShow (compileProfile (spec & #frontmatter .~ times))+ timeConcept <- profileConcept "time" [("start", String "2026-10-02T00:00:00Z"), ("end", String "2026-10-01T00:00:00Z")] ""+ assertEqual 1 (length (validateProfile PermissiveConformance rootTimes [timeConcept]))+ assertEqual 2 (Map.size (conceptResolvedSelectors (compiledProfileBaseConstraints rootTimes)))+ for_ [".inf", "-.inf", ".nan"] $ \spelling ->+ case testConceptWithFrontmatter "nonfinite" ("type: Job\noutcomes: [{metric: duration, unit: s, direction: down, method: observe, target: " <> spelling <> "}]\n") of+ Left _ -> Right () -- parser rejection is an accepted outcome+ Right concept -> if null (validateProfile PermissiveConformance compiled [concept]) then Left "nonfinite YAML accepted" else Right ()+ invalid (root & #membershipConstraints .~ [MembershipConstraint (select "jobs") (select "jobRefs")])+ invalid (root & #membershipConstraints .~ [MembershipConstraint (ValueSelector "jobs" (Just "unknown")) (select "jobRefs")])+ invalid (root & #presenceConstraints .~ [pc & #requireAll .~ [select "sources", select "sources"]])+ invalid (root & #presenceConstraints .~ [pc & #requireAll .~ [select ""]])+ let siblingA = nestedFieldRule "a" Nothing [] Scalar Nothing Nothing Nothing+ siblingB = nestedFieldRule "b" Nothing [] Scalar Nothing Nothing Nothing+ recordGate = PresenceConstraint Nothing [select "b"] [select "a"] []+ elementRules = NestedRules True [] [] [siblingA, siblingB] [recordGate] []+ objectRules = NestedRules True [] [] [siblingA, siblingB] [] []+ dual = (fieldRule "dual" Nothing [] Any Nothing (Just elementRules) Nothing Nothing) & #objectFields .~ Just objectRules+ dualSpec = spec & #frontmatter .~ (emptyTestFrontmatterRules & #optional .~ [dual])+ dualCompiled <- firstShow (compileProfile dualSpec)+ dualRule <- maybe (Left "dual field missing") Right (Map.lookup "dual" (compiledProfileBaseRules dualCompiled))+ assertEqual (fieldRuleElementConstraints dualRule) (fieldRuleObjectConstraints dualRule)+ assertEqual [recordGate] (conceptPresenceConstraints (fieldRuleObjectConstraints dualRule))+ for_ [rec [("b", String "present")], arr [rec [("b", String "present")]]] $ \value -> do+ concept <- profileConcept "dual" [("dual", value)] ""+ assertEqual 1 (length (validateProfile PermissiveConformance dualCompiled [concept]))+ rendered <- firstShow (renderProfileDocumentation defaultDocumentationOptions dualCompiled)+ let prose = Text.concat (map serializeConcept rendered)+ assertEqual 1 (length (Text.breakOnAll "require all [a]" prose))++-- Exercise exact file decoding, width-extracting registry decoding, and local+-- discovery; the root-level closure flag in the frozen fixture is intentional.+testNestedClosureDecoding :: IO (Either Text ())+testNestedClosureDecoding = do+ currentPath <- fixtureFilePath "profiles/nested-closure.dhall"+ oldPath <- fixtureFilePath "profiles/pre-nested-closure.dhall"+ registryPath <- fixtureFilePath "profiles/nested-closure-registry.dhall"+ malformedPath <- fixtureFilePath "profiles/nested-closure-malformed.dhall"+ current <- loadProfileFile currentPath+ old <- loadProfileFile oldPath+ registry <- loadRegistry (RegistryFile registryPath)+ oldRegistry <- loadRegistry (RegistryFile oldPath)+ malformed <- loadRegistry (RegistryFile malformedPath)+ malformedFile <- loadProfileFile malformedPath+ malformedTypePath <- fixtureFilePath "profiles/nested-closure-malformed-type.dhall"+ malformedType <- loadRegistry (RegistryFile malformedTypePath)+ discovered <- traverse ProfileDiscovery.fileQualifiesAsProfileDescriptor [currentPath, oldPath, malformedPath]+ pure $ do+ spec <- current+ oldSpec <- old+ compiled <- firstShow (compileProfile spec)+ oldCompiled <- firstShow (compileProfile oldSpec)+ entries <- firstShow registry+ assertEqual ["nestedClosure"] (map (^. #export) entries)+ assertEqual [spec] (map (^. #spec) entries)+ historical <- firstShow oldRegistry+ assertEqual [oldSpec] (map (^. #spec) historical)+ assertEqual [True, True, False] discovered+ assertEqual False (oldSpec ^. #allowUnknownFields)+ assertEqual [] =<< firstShow malformed+ assertEqual [] =<< firstShow malformedType+ case malformedFile of+ Left _ -> Right ()+ Right _ -> Left "malformed descriptor loaded through a frozen decoder"+ for_ [compiledProfileBaseRules compiled, compiledProfileRulesForType compiled "Note"] $ \rules -> do+ rows <- lookupCompiledRule "rows" rules+ assertEqual (Just False) (fieldRuleElementAllowsUnknownFields rows)+ rendered <- firstShow (renderProfileDocumentation defaultDocumentationOptions compiled)+ let documentation = Text.concat (map serializeConcept rendered)+ for_ ["- Element field names: closed", "- Object field names: closed", "- Element field names: none", "note"] $ \expected ->+ assertBool ("missing compiled documentation: " <> expected) (expected `Text.isInfixOf` documentation)+ assertEqual rendered =<< firstShow (renderProfileDocumentation defaultDocumentationOptions compiled)+ for_ [compiledProfileBaseRules oldCompiled, compiledProfileRulesForType oldCompiled "Note"] $ \rules -> do+ rows <- lookupCompiledRule "rows" rules+ settings <- lookupCompiledRule "settings" rules+ assertEqual (Just True) (fieldRuleElementAllowsUnknownFields rows)+ assertEqual (Just True) (fieldRuleObjectAllowsUnknownFields settings)+ for_ [fieldRuleElementConstraints rows, fieldRuleObjectConstraints settings] $ \constraints -> do+ assertEqual 1 (length (conceptPresenceConstraints constraints))+ assertEqual [TimestampOrderConstraint "start" "end"] (conceptTimestampOrderConstraints constraints)+ score <- maybe (Left "frozen nested members missing") (lookupCompiledRule "score") (fieldRuleElementFields rows)+ assertEqual (Just FiniteDecimal) (fieldRuleFormat score)+ for_ [compiledProfileBaseConstraints oldCompiled, compiledProfileConstraintsForType oldCompiled "Note"] $ \constraints -> do+ assertEqual 1 (length (conceptPresenceConstraints constraints))+ assertEqual 1 (length (conceptTimestampOrderConstraints constraints))+ assertEqual 1 (length (conceptMembershipConstraints constraints))+ assertEqual 1 (length (conceptUniqueValuesConstraints constraints))++-- A public diagnostic slice: one supplied unknown key, independent of values.+testNestedClosureValidation :: Either Text ()+testNestedClosureValidation = do+ let members = NestedRules False [nestedFieldRule "name" Nothing [] Scalar Nothing Nothing Nothing] [] [] [] []+ spec = nestedProfileWithRules Any members Nothing+ validate rawSpec fields = do+ compiled <- firstShow (compileProfile rawSpec)+ concept <- profileConcept "one" (("type", String "Reviewed Concept") : fields) ""+ pure (validateProfile PermissiveConformance compiled [concept])+ row values = ("reviews", toJSON [object values])+ cid <- firstShow (parseConceptId "one")+ violations <- validate spec [row ["name" .= ("a" :: Text), "stray" .= ("x" :: Text)]]+ assertEqual [NestedFieldNotInProfile cid (FieldPath (FieldName "reviews" :| [ArrayIndex 0, FieldName "stray"]))] violations+ assertEqual [] =<< validate spec [row ["name" .= ("a" :: Text)]]+ let open = members & #allowUnknownFields .~ True+ declared = members & #optional .~ [nestedFieldRule "stray" Nothing [] Scalar Nothing Nothing Nothing]+ for_ [open, declared] $ \rules ->+ assertEqual [] =<< validate (nestedProfileWithRules Any rules Nothing) [row ["name" .= ("a" :: Text), "stray" .= ("x" :: Text)]]++-- The matrix isolates scope merging, record shape, and presence from closure.+testNestedClosureMatrix :: Either Text ()+testNestedClosureMatrix = do+ cid <- firstShow (parseConceptId "one")+ let scalar key = nestedFieldRule key Nothing [] Scalar Nothing Nothing Nothing+ rules policy names = NestedRules policy [] [] (map scalar names) [] []+ parent elements objects = (requiredField "value") & #elementFields .~ elements & #objectFields .~ objects+ front field = emptyTestFrontmatterRules & #optional .~ [field]+ specFor elements objects = objectProfileWithRules "value" Any objects elements & #okfVersion .~ "0.2"+ typed base field = base & #types .~ [TypeRule "Note" Nothing Nothing (front field) Nothing Nothing False [] Nothing]+ record = object . map (\key -> (key, String "present"))+ array :: [Value] -> Value+ array values = toJSON values+ validate mode spec values = do+ compiled <- firstShow (compileProfile spec)+ concept <- profileConcept "one" values ""+ pure (validateProfile mode compiled [concept])+ check spec value = validate PermissiveConformance spec [("type", String "Note"), ("value", value)]+ unknowns = mapMaybe (\case NestedFieldNotInProfile _ path -> Just path; _ -> Nothing)+ path segments = FieldPath (FieldName "value" :| segments)+ violationsFor shape key = [path (if shape then [ArrayIndex 0, FieldName key] else [FieldName key])]+ shapeValue shape value = if shape then array [value] else value+ inspect shape = if shape then fieldRuleElementAllowsUnknownFields else fieldRuleObjectAllowsUnknownFields+ shapeRules shape = if shape then fieldRuleElementFields else fieldRuleObjectFields+ mkParent shape r = if shape then parent (Just r) Nothing else parent Nothing (Just r)+ mkSpec shape r = if shape then specFor (Just r) Nothing else specFor Nothing (Just r)+ noShapeCompiled <- firstShow (compileProfile (specFor Nothing Nothing))+ noShapeRule <- lookupCompiledRule "value" (compiledProfileBaseRules noShapeCompiled)+ assertEqual Nothing (fieldRuleElementAllowsUnknownFields noShapeRule)+ assertEqual Nothing (fieldRuleObjectAllowsUnknownFields noShapeRule)+ for_ [True, False] $ \shape -> do+ -- All boolean pairs, union of names, absence as identity, and unknown types.+ for_ [(a, b) | a <- [True, False], b <- [True, False]] $ \(a, b) -> do+ let base = mkSpec shape (rules a ["base"])+ spec = typed base (mkParent shape (rules b ["typed"]))+ compiled <- firstShow (compileProfile spec)+ effective <- lookupCompiledRule "value" (compiledProfileRulesForType compiled "Note")+ assertEqual (Just (a && b)) (inspect shape effective)+ members <- maybe (Left "expected nested map") Right (shapeRules shape effective)+ assertEqual ["base", "typed"] (Map.keys members)+ assertEqual [] =<< check spec (shapeValue shape (record ["base", "typed"]))+ rejected <- check spec (shapeValue shape (record ["base", "typed", "stray"]))+ assertEqual (if a && b then [] else violationsFor shape "stray") (unknowns rejected)+ for_ [typed base (requiredField "value"), typed (specFor Nothing Nothing) (mkParent shape (rules a ["base"]))] $ \identitySpec -> do+ identityCompiled <- firstShow (compileProfile identitySpec)+ identityRule <- lookupCompiledRule "value" (compiledProfileRulesForType identityCompiled "Note")+ assertEqual (Just a) (inspect shape identityRule)+ unknownType <- validate PermissiveConformance spec [("type", String "Unknown"), ("value", shapeValue shape (record ["base", "typed"]))]+ assertEqual (if a then [] else violationsFor shape "typed") (unknowns unknownType)+ for_ [True, False] $ \open -> do+ emptyCompiled <- firstShow (compileProfile (mkSpec shape (rules open [])))+ emptyRule <- lookupCompiledRule "value" (compiledProfileBaseRules emptyCompiled)+ assertEqual (Just open) (inspect shape emptyRule)+ emptyDocumentation <- firstShow (renderProfileDocumentation defaultDocumentationOptions emptyCompiled)+ let expected = (if shape then "- Element field names: " else "- Object field names: ") <> if open then "open" else "closed"+ assertBool "empty map lost closure in documentation" (expected `Text.isInfixOf` Text.concat (map serializeConcept emptyDocumentation))+ -- An empty closed declaration forbids every member, including core/ID names.+ let closedEmpty = mkSpec shape (rules False []) & #idField .~ Just "docId"+ rejected <- check closedEmpty (shapeValue shape (record ["title", "docId"]))+ assertEqual (map (\key -> head (violationsFor shape key)) ["docId", "title"]) (unknowns rejected)+ for_ [[], [("value", shapeValue shape (object []))], [("value", if shape then array [] else object [])]] $ \values -> do+ absent <- validate StrictAuthoring closedEmpty (("type", String "Note") : values)+ assertEqual [] (unknowns absent)+ -- Required parent remains missing; a list containing an empty record is present.+ assertEqual (if shape && values == [("value", array [object []])] then [] else [MissingProfileField cid "value" Nothing]) absent+ -- The same declared rules work for either shape and retain ordinary checks.+ let kind = (scalar "kind") & #allowedValues .~ ["human", "model"]+ conditional = FieldCondition "kind" ["model"]+ provider = (scalar "provider") & #when .~ Just conditional & #allowedValues .~ ["openai"]+ recommended = (scalar "audit") & #when .~ Just conditional+ conditionalRules = NestedRules False [kind, provider] [recommended] [scalar "note"] [] []+ conditionalSpec = mkSpec shape conditionalRules+ human = object ["kind" .= ("human" :: Text), "provider" .= ("openai" :: Text), "audit" .= True, "note" .= ("ok" :: Text)]+ assertEqual [] =<< check conditionalSpec (shapeValue shape human)+ invalidKnown <- check conditionalSpec (shapeValue shape (object ["kind" .= ("human" :: Text), "provider" .= ("bad" :: Text)]))+ assertEqual [] (unknowns invalidKnown)+ assertEqual 1 (length invalidKnown)+ for_ [PermissiveConformance, StrictAuthoring] $ \mode -> do+ active <- validate mode conditionalSpec [("type", String "Note"), ("value", shapeValue shape (record ["note"]))]+ -- Unknown checking does not manufacture keys or replace missing presence.+ assertEqual [] (unknowns active)+ model <- validate mode conditionalSpec [("type", String "Note"), ("value", shapeValue shape (object ["kind" .= ("model" :: Text), "provider" .= ("openai" :: Text), "stray" .= Null]))]+ assertEqual (violationsFor shape "stray") (unknowns model)+ assertEqual (if mode == StrictAuthoring then 2 else 1) (length model)+ let invalidOptional = conditionalRules & #optional .~ [(scalar "note") & #when .~ Just conditional]+ case compileProfile (mkSpec shape invalidOptional) of+ Right _ -> Left "closure allowed an optional condition"+ Left errs -> assertBool "expected OptionalFieldWithCondition" (any (\case OptionalFieldWithCondition {} -> True; _ -> False) errs)+ -- A false parent condition changes only presence; supplied children stay closed.+ let gate = (requiredField "mode") & #cardinality .~ Scalar & #allowedValues .~ ["on", "off"]+ falseParent = (mkParent shape (rules False [])) & #when .~ Just (FieldCondition "mode" ["on"])+ gated = (specFor Nothing Nothing) & #frontmatter .~ (emptyTestFrontmatterRules & #required .~ [falseParent] & #optional .~ [gate])+ gatedViolations <- validate PermissiveConformance gated [("mode", String "off"), ("value", shapeValue shape (record ["stray"]))]+ assertEqual (violationsFor shape "stray") (unknowns gatedViolations)+ -- Separate policies for two fields and both accepted shapes of one field.+ for_ [(a, b) | a <- [False, True], b <- [False, True]] $ \(elementOpen, objectOpen) -> do+ let elementRules = rules elementOpen ["name"]+ objects = rules objectOpen ["name"]+ dual = specFor (Just elementRules) (Just objects)+ separate = dual & #frontmatter .~ (emptyTestFrontmatterRules & #optional .~ [parent (Just elementRules) Nothing, (parent Nothing (Just objects)) & #field .~ "settings"])+ compiled <- firstShow (compileProfile dual)+ effective <- lookupCompiledRule "value" (compiledProfileBaseRules compiled)+ assertEqual (Just elementOpen) (fieldRuleElementAllowsUnknownFields effective)+ assertEqual (Just objectOpen) (fieldRuleObjectAllowsUnknownFields effective)+ for_ [(True, elementOpen), (False, objectOpen)] $ \(shape, open) -> do+ actual <- check dual (shapeValue shape (record ["name", "stray"]))+ assertEqual (if open then [] else violationsFor shape "stray") (unknowns actual)+ combined <- validate PermissiveConformance separate [("value", array [record ["stray"]]), ("settings", record ["stray"])]+ assertEqual ((if elementOpen then 0 else 1) + (if objectOpen then 0 else 1)) (length (unknowns combined))+ -- Sorting is per record, indices are stable, null is still a key, Unicode survives.+ let closed = specFor (Just (rules False ["name"])) Nothing+ mixed = array [object ["z" .= Null, "a" .= ("" :: Text), "名前" .= ([] :: [Text])], record ["again"], Number 1]+ mixedViolations <- check closed mixed+ assertEqual+ [NestedFieldNotInProfile cid (path [ArrayIndex 0, FieldName key]) | key <- ["a", "z", "名前"]]+ (take 3 mixedViolations)+ assertEqual [NestedFieldNotInProfile cid (path [ArrayIndex 1, FieldName "again"]), NestedElementNotRecord cid (path [ArrayIndex 2]) (Number 1)] (drop 3 mixedViolations)+ -- Closure and predecessor constraints coexist after scope merging.+ let constrained =+ (rules False ["trigger", "required", "start", "end"])+ & #optional+ .~ [scalar "trigger", scalar "required", (scalar "start") & #format .~ Just Rfc3339Utc, (scalar "end") & #format .~ Just Rfc3339Utc]+ & #presenceConstraints+ .~ [PresenceConstraint Nothing [ValueSelector "trigger" Nothing] [ValueSelector "required" Nothing] []]+ & #timestampOrderConstraints+ .~ [TimestampOrderConstraint "start" "end"]+ spec = typed (specFor (Just constrained) Nothing) (parent (Just (rules True ["typed"])) Nothing)+ coexist <- check spec (array [object ["trigger" .= True, "start" .= ("2026-10-02T00:00:00Z" :: Text), "end" .= ("2026-10-01T00:00:00Z" :: Text), "stray" .= Null]])+ assertEqual (violationsFor True "stray") (unknowns coexist)+ assertEqual 1 (length [() | ConstraintPresenceMissing {} <- coexist])+ assertEqual 1 (length [() | TimestampOrderViolation {} <- coexist])
+ test/fixtures/catalogue/Profile/Hypothesis.dhall view
@@ -0,0 +1,43 @@+--| A falsifiable hypothesis shared by coordinated campaigns and initiatives.+let okf = ./okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let Cardinality = okf.Cardinality++in FieldRule::{+ , field = "hypothesis"+ , description = Some+ "The problem, belief, prediction, and falsification condition."+ , cardinality = Cardinality.Any+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "problem"+ , description = Some "The observed problem being addressed."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "belief"+ , description = Some "The causal belief being tested."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "prediction"+ , description = Some+ "The observable result expected if the belief holds."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "falsifiedIf"+ , description = Some "The observation that would falsify the belief."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }
test/fixtures/catalogue/Profile/V02.dhall view
@@ -32,12 +32,15 @@ -- ## Policy one: where the house `status` key collides, the house key wins -- -- OKF v0.2 §5.4 gives `status` the vocabulary `draft` / `stable` / `deprecated`.--- Nine profiles in this repository use the same key name for a house lifecycle--- vocabulary — five that predate v0.2, and four introduced after it that answer+-- Nineteen profiles in this repository use the same key name for a house lifecycle+-- vocabulary — five that predate v0.2, and fourteen introduced after it that answer -- a question v0.2's vocabulary cannot: -- -- * `documentation.architectureDecisions` — `Accepted`, and siblings -- * `documentation.patternCatalog` — `current`, `deprecated`+-- * `documentation.productResearch` — study `planned`, `active`,+-- `complete`, `cancelled`; finding+-- `current`, `superseded`, `withdrawn` -- * `documentation.researchDocuments` — `active`, `complete`, `superseded` -- * `documentation.specifications` — `draft`, `proposed`, `ratified`, -- `superseded`, `withdrawn`@@ -51,8 +54,32 @@ -- * `coordination.bugReports` — `reported`, `confirmed`, `in-progress`, -- `fixed`, `wont-fix`, `duplicate`, -- `not-a-bug`, `cannot-reproduce`+-- * `coordination.jobs` — `draft`, `active`, `retired`, plus a+-- second house key, `validationStatus`:+-- `untested`, `supported`, `mixed`,+-- `challenged`+-- * `coordination.features` — `discovered`, `planned`, `in-progress`,+-- `delivered`, `blocked`, `deferred`,+-- `retired`+-- * `coordination.designs` — brief and breadboard `draft`,+-- `validated`, `retired`; detailed+-- design `exploring`, `chosen`,+-- `designed`, `retired`+-- * `coordination.frictionLog` — `logged`, `promoted`, `resolved`,+-- `accepted`, `duplicate` ----- Those nine keep their house vocabulary and do **not** splice in `status` or+-- * `coordination.campaigns` — campaign and email lifecycles+-- * `coordination.audiences` — segment/persona lifecycles and+-- persona validationStatus+--+-- * `coordination.strategy` — theme, objective and initiative lifecycles+-- * `coordination.launches` — launch and page publication lifecycles+-- * `documentation.messaging` — positioning and message lifecycles+--+-- `assurance.claimChecks` uses its house verdict as a complete result+-- vocabulary, taking neither status nor staleAfter (ADR-21).+--+-- Those nineteen keep their house vocabulary and do **not** splice in `status` or -- `staleAfter` from this module. This is sanctioned rather than tolerated: a -- profile key name does not imply the OKF core key of that name, and okf never -- rejects a profile over it. What okf checks instead is value *formats*, because
test/fixtures/catalogue/package.dhall view
@@ -1,4 +1,4 @@---| Offline snapshot of mori://shinzui/okf-profiles at v0.19.0.+--| Offline snapshot of mori://shinzui/okf-profiles at v0.25.0. -- Generated by scripts/refresh-default-registry.sh; do not edit by hand. -- Profile/okf.dhall is the resolved schema pinned by that release, so this -- fixture evaluates without network access.@@ -8,12 +8,12 @@ -- ready-made profiles. With a versioned, hash-pinned remote import: -- -- let okf =--- https://raw.githubusercontent.com/shinzui/okf-profiles/v0.19.0/package.dhall--- sha256:85176d78369b6d73c9f13c30277903b629d6bf048a4c7d71fc26e68b99c3eaa6+-- https://raw.githubusercontent.com/shinzui/okf-profiles/v0.25.0/package.dhall+-- sha256:ec74cc6f7b8ee1a5af81ee7fc8804f35cf2c9ba4010671d9e881da72eb6719d3 -- -- in okf.postgresql // { name = "acme-warehouse" } ----- v0.19.0 requires okf 0.9.0.0 or later. See README.md for how to generate the real hash (`dhall freeze`) and for the+-- v0.25.0 requires okf 0.9.0.0 or later. See README.md for how to generate the real hash (`dhall freeze`) and for the -- public-repo / pinning rationale. let okf = ./Profile/okf.dhall @@ -30,6 +30,7 @@ , FieldFormat = okf.FieldFormat , mk = okf.mk , reviewRule = ./Profile/ReviewRule.dhall+ , hypothesis = ./Profile/Hypothesis.dhall , modelReview = ./Profile/ModelReview.dhall , v02 = ./Profile/V02.dhall , assurance = ./profiles/assurance/package.dhall
+ test/fixtures/catalogue/profiles/assurance/claim-checks.dhall view
@@ -0,0 +1,183 @@+--| A Claim Check is assurance: it records that wording was tested against data.+-- It differs from verificationEvidence (software runs) and product-research Findings+-- (customer observations). measured is evidence for copy, not a job-outcome measure.+-- verdict is the house result vocabulary; this result record deliberately takes+-- neither OKF status nor staleAfter, as event assurance already does (ADR-15).+-- No recommended fields and no legacyTimestamp: unchecked drafts have no evidence,+-- and the shared type starts at v0.2. Foreign concept references are canonical;+-- query/computation alternatives and target types belong to check-gtm.py.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "CLM"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++in Profile::{+ , name = "claim-checks"+ , description = Some+ "Claim Checks record what wording was checked, by whom, against what evidence, and the result."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "claimId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Claim Check"+ , description = Some+ "Evidence that a specific statement was checked against data."+ , idPrefix = Some "CLM"+ , pathPattern = Some "**"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "claimId"+ , description = Some "Bundle-scoped stable CLM-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "CLM")+ }+ , FieldRule::{+ , field = "statement"+ , description = Some "Exact wording that was checked."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "usedIn"+ , description = Some+ "Canonical concept URIs or repository paths where the statement is used."+ , cardinality = Cardinality.List+ }+ , FieldRule::{+ , field = "verdict"+ , description = Some+ "Result of comparing the statement with evidence."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "unchecked", "match", "close", "wrong", "cannot-verify" ]+ }+ , FieldRule::{+ , field = "checkedBy"+ , description = Some "Actor who performed the check."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Actor+ , when = Some+ { field = "verdict"+ , hasValue = [ "match", "close", "wrong", "cannot-verify" ]+ }+ }+ , FieldRule::{+ , field = "measured"+ , description = Some "What the evidence actually establishes."+ , cardinality = Cardinality.Scalar+ , when = Some+ { field = "verdict", hasValue = [ "match", "close", "wrong" ] }+ }+ , FieldRule::{+ , field = "asOf"+ , description = Some "UTC time the measurement describes."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ , when = Some+ { field = "verdict", hasValue = [ "match", "close", "wrong" ] }+ }+ , FieldRule::{+ , field = "suggestedWording"+ , description = Some "Accurate replacement wording."+ , cardinality = Cardinality.Scalar+ , when = Some+ { field = "verdict", hasValue = [ "close", "wrong" ] }+ }+ , FieldRule::{+ , field = "unverifiableBecause"+ , description = Some "Why this statement cannot be verified."+ , cardinality = Cardinality.Scalar+ , when = Some+ { field = "verdict", hasValue = [ "cannot-verify" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "query"+ , description = Some+ "Repository-relative file holding the checking query."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "computationRef"+ , description = Some "Canonical Attested Computation URI."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.UriWithScheme "mori")+ }+ , FieldRule::{+ , field = "stated"+ , description = Some "The value as stated in the copy."+ , cardinality = Cardinality.Scalar+ }+ ]+ }+ , guidance = Some+ "The opt-in check-gtm.py requires query or computation evidence for measured verdicts and verifies referenced artifact targets and local query files."+ }+ ]+ }
test/fixtures/catalogue/profiles/assurance/package.dhall view
@@ -14,7 +14,9 @@ -- failure mode. -- `verificationEvidence` records a claim about a system being put to the test -- and what the run and independent verifier concluded.-{ reviews = ./reviews.dhall+-- Claim checks record a test of wording against data, not a software run.+{ claimChecks = ./claim-checks.dhall+, reviews = ./reviews.dhall , failureModes = ./failure-modes.dhall , verificationEvidence = ./verification-evidence.dhall }
+ test/fixtures/catalogue/profiles/coordination/audiences.dhall view
@@ -0,0 +1,305 @@+--| Segments and personas describe the consumer side beside jobs. A persona is+-- not a job and never restates it. Its assessment rule is copied exactly from+-- jobs.dhall; keep the two policies in sync until factored into a shared block.+-- External-only references use SEG plus canonical foreign-handle patterns.+-- Nothing is recommended: untested drafts need no assessment or sources. House+-- status replaces OKF status/staleAfter. No legacyTimestamp exists for this new+-- v0.2 profile. Target type and query-file checks run in opt-in check-gtm.py.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "SEG"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++let nestedScalar =+ \(name : Text) ->+ \(description : Text) ->+ NestedFieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let assessment =+ FieldRule::{+ , field = "assessment"+ , description = Some+ "The authors' synthesis of research findings behind `validationStatus`."+ , objectFields = Some NestedRules::{+ , required =+ [ nestedScalar+ "rationale"+ "Why the cited findings justify the declared validation status."+ , nestedScalar+ "assessedAt"+ "UTC RFC3339 time the assessment was made."+ // { format = Some FieldFormat.Rfc3339Utc }+ , NestedFieldRule::{+ , field = "findingRefs"+ , description = Some+ "Canonical Mori URIs of the product-research findings assessed."+ , cardinality = Cardinality.List+ , reference = externalOnly "PR"+ }+ ]+ }+ , when = Some+ { field = "validationStatus"+ , hasValue = [ "supported", "mixed", "challenged" ]+ }+ }++in Profile::{+ , name = "audiences"+ , description = Some+ "Segments define populations; Personas name who holds jobs without restating those jobs."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "audienceId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Segment"+ , description = Some+ "A precisely defined population for coordinated work."+ , idPrefix = Some "SEG"+ , pathPattern = Some "segments/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "audienceId"+ , description = Some "Bundle-scoped stable SEG-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "SEG")+ }+ , FieldRule::{+ , field = "definition"+ , description = Some+ "Population definition precise enough to query."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Segment lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "draft", "active", "retired" ]+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "criteria"+ , description = Some+ "Structured selectors with producer-owned vocabularies."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "dimension"+ , description = Some "Dimension being selected."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "operator"+ , description = Some "Selection operation."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "value"+ , description = Some "Selection value."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "size"+ , description = Some "Segment size and how it was obtained."+ , cardinality = Cardinality.Any+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "count"+ , description = Some "Numeric population count."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "asOf"+ , description = Some "Time of the count."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , NestedFieldRule::{+ , field = "method"+ , description = Some "How the count was obtained."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "query"+ , description = Some+ "Repository-relative file defining membership."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "termRefs"+ , description = Some "Canonical vocabulary terms."+ , cardinality = Cardinality.List+ , reference = externalOnly "TERM"+ }+ , FieldRule::{+ , field = "parentSegmentRef"+ , description = Some "Broader Segment."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "SEG"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "SEG")+ }+ }+ ]+ }+ }+ , TypeRule::{+ , type = "Persona"+ , description = Some+ "A research-assessed role and context; a persona is not a job."+ , idPrefix = Some "PERSONA"+ , pathPattern = Some "personas/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "audienceId"+ , description = Some "Bundle-scoped stable PERSONA-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "PERSONA")+ }+ , FieldRule::{+ , field = "role"+ , description = Some "Role represented by the persona."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "context"+ , description = Some "Circumstances in which the persona acts."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Persona lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "draft", "active", "retired" ]+ }+ , FieldRule::{+ , field = "validationStatus"+ , description = Some "Research support for this persona."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "untested", "supported", "mixed", "challenged" ]+ }+ , assessment+ ]+ , optional =+ [ FieldRule::{+ , field = "jobRefs"+ , description = Some "Jobs held by this persona."+ , cardinality = Cardinality.List+ , reference = externalOnly "JOB"+ }+ , FieldRule::{+ , field = "segmentRefs"+ , description = Some "Segments containing this persona."+ , cardinality = Cardinality.List+ , reference = Some HandleReferenceRule::{+ , localPrefix = "SEG"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "SEG")+ }+ }+ , FieldRule::{+ , field = "goals"+ , description = Some "Producer-described goals."+ , cardinality = Cardinality.List+ }+ , FieldRule::{+ , field = "objections"+ , description = Some "Producer-described objections."+ , cardinality = Cardinality.List+ }+ , FieldRule::{+ , field = "channels"+ , description = Some "Channels used by the persona."+ , cardinality = Cardinality.List+ }+ ]+ }+ , guidance = Some+ "The opt-in check-gtm.py verifies that assessment references name current Findings and that other concept targets have the required types."+ }+ ]+ }
+ test/fixtures/catalogue/profiles/coordination/campaigns.dhall view
@@ -0,0 +1,466 @@+--| Campaigns are coordinated work with a lifecycle, beside features and designs.+-- Readouts reuse product-research Studies and Findings; reviews use ReviewRule.+-- External-only references use this profile’s CMP prefix as the engine requires,+-- then constrain the foreign handle by a canonical URI pattern. Nothing is+-- recommended: draft campaigns may have no review or evidence. Status is a house+-- vocabulary, so OKF status/staleAfter are absent (ADR-1). legacyTimestamp is absent+-- because this shared profile starts at v0.2. Unsupported conjunctions and target+-- types are enforced by the opt-in check-gtm.py, not a wider schema.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "CMP"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++let hypothesis = ../../Profile/Hypothesis.dhall++in Profile::{+ , name = "campaigns"+ , description = Some+ "Campaigns and their emails coordinate purpose, audience, delivery and research evaluation."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "campaignId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Campaign"+ , description = Some+ "Coordinated work carrying a hypothesis, announcement, or engagement purpose."+ , idPrefix = Some "CMP"+ , pathPattern = Some "*/campaign"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "campaignId"+ , description = Some "Bundle-scoped stable CMP-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "CMP")+ }+ , FieldRule::{+ , field = "purpose"+ , description = Some+ "Primary purpose of the coordinated campaign."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "hypothesis", "announcement", "engagement" ]+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Campaign coordination lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "drafting"+ , "in-review"+ , "approved"+ , "sending"+ , "measuring"+ , "read-out"+ , "cancelled"+ ]+ }+ , FieldRule::{+ , field = "owners"+ , description = Some+ "Actors or canonical Mori URIs accountable for the campaign."+ , cardinality = Cardinality.List+ }+ , hypothesis+ // { when = Some+ { field = "purpose", hasValue = [ "hypothesis" ] }+ }+ , FieldRule::{+ , field = "announcement"+ , description = Some+ "Announced features and intended availability."+ , cardinality = Cardinality.Any+ , when = Some { field = "purpose", hasValue = [ "announcement" ] }+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "featureRefs"+ , description = Some+ "Canonical URIs of the announced Features."+ , cardinality = Cardinality.List+ , reference = externalOnly "FEAT"+ }+ , NestedFieldRule::{+ , field = "liveBy"+ , description = Some "Intended availability deadline."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "engagement"+ , description = Some "Behavior this campaign seeks and why."+ , cardinality = Cardinality.Any+ , when = Some { field = "purpose", hasValue = [ "engagement" ] }+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "behavior"+ , description = Some "The behavior to encourage."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "why"+ , description = Some "Why the behavior matters."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "readout"+ , description = Some+ "Decision and research findings evaluating the campaign."+ , cardinality = Cardinality.Any+ , when = Some { field = "status", hasValue = [ "read-out" ] }+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "decision"+ , description = Some+ "Decision taken in light of the findings."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "findingRefs"+ , description = Some "Canonical result Finding URIs."+ , cardinality = Cardinality.List+ , reference = externalOnly "PR"+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "cancellationReason"+ , description = Some "Why the campaign was cancelled."+ , cardinality = Cardinality.Scalar+ , when = Some { field = "status", hasValue = [ "cancelled" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "secondaryPurposes"+ , description = Some+ "Additional purposes whose blocks also apply."+ , cardinality = Cardinality.List+ , allowedValues = [ "hypothesis", "announcement", "engagement" ]+ }+ , FieldRule::{+ , field = "audienceRefs"+ , description = Some "Canonical audience Segment URIs."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "audience"+ , description = Some+ "Inline audience definitions for a corpus without a Segment bundle."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "segment"+ , description = Some "Segment name."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "definition"+ , description = Some "Queryable population definition."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "size"+ , description = Some "Population size at the stated time."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "asOf"+ , description = Some "Time the population size describes."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "primaryMetric"+ , description = Some+ "Primary evaluation metric; observed values belong in findings or claim checks."+ , cardinality = Cardinality.Any+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "name"+ , description = Some "Stable name for this metric."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "metric"+ , description = Some "What is counted or measured."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "unit"+ , description = Some "Unit of measurement."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "direction"+ , description = Some "Desired direction of change."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "increase", "decrease", "maintain" ]+ }+ , NestedFieldRule::{+ , field = "method"+ , description = Some "How the metric is obtained."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "population"+ , description = Some "Population described by the metric."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "period"+ , description = Some "Observation period for the baseline."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional =+ [ NestedFieldRule::{+ , field = "baseline"+ , description = Some+ "Numeric starting value; evidence, not a target."+ , cardinality = Cardinality.Scalar+ }+ ]+ }+ }+ , FieldRule::{+ , field = "window"+ , description = Some "Campaign evaluation interval."+ , cardinality = Cardinality.Any+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "from"+ , description = Some "Start of the evaluation window."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , NestedFieldRule::{+ , field = "to"+ , description = Some "End of the evaluation window."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "outcomeRefs"+ , description = Some+ "Named job outcomes this work is intended to move."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "jobRef"+ , description = Some "Canonical job URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "JOB"+ }+ , NestedFieldRule::{+ , field = "outcomeName"+ , description = Some+ "Name of an outcome declared by that job."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "jobRefs"+ , description = Some "Customer jobs this campaign serves."+ , cardinality = Cardinality.List+ , reference = externalOnly "JOB"+ }+ , FieldRule::{+ , field = "studyRef"+ , description = Some "Canonical experiment Study URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "PR"+ }+ , FieldRule::{+ , field = "launchRef"+ , description = Some "Canonical Launch URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "LAUNCH"+ }+ , FieldRule::{+ , field = "chapters"+ , description = Some "Chapter labels in scope."+ , cardinality = Cardinality.List+ }+ ]+ }+ , guidance = Some+ "Approval and sending require the responsible owners’ actual review and send evidence; dates alone never promote the lifecycle. The opt-in check-gtm.py verifies secondary purpose blocks, experiment-study targets, result-finding targets, metric context and reference targets that descriptors cannot express."+ }+ , TypeRule::{+ , type = "Campaign Email"+ , description = Some+ "One email or regional variant in a coordinated campaign."+ , idPrefix = Some "EMAIL"+ , pathPattern = Some "*/emails/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "campaignId"+ , description = Some "Bundle-scoped stable EMAIL-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "EMAIL")+ }+ , FieldRule::{+ , field = "campaignRef"+ , description = Some "Campaign this email belongs to."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "CMP"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "CMP")+ }+ }+ , FieldRule::{+ , field = "sequence"+ , description = Some+ "Ordering within the campaign; variants may share it."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Email preparation and send lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "draft", "approved", "scheduled", "sent", "cancelled" ]+ }+ , FieldRule::{+ , field = "sentAt"+ , description = Some "Actual send time."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ , when = Some { field = "status", hasValue = [ "sent" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "subject"+ , description = Some "Chosen subject line."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "subjectVariants"+ , description = Some "Producer-owned subject variants."+ , cardinality = Cardinality.Any+ }+ , FieldRule::{+ , field = "cta"+ , description = Some "Call to action."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "sendBy"+ , description = Some "Planned send deadline."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , FieldRule::{+ , field = "audienceRefs"+ , description = Some "Audience Segment URIs."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "landingPageRef"+ , description = Some "Canonical Landing Page URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "LP"+ }+ ]+ }+ }+ ]+ }
test/fixtures/catalogue/profiles/coordination/capabilities.dhall view
@@ -13,7 +13,11 @@ -- that omission is the profile's single most load-bearing decision. -- * A capability that only exists when several repositories cooperate is NOT a -- capability record either — no single repository can assert it or prove it.--- It belongs to the consuming repository as a use-case feature.+-- It belongs to the consumer as product behavior: a standalone feature+-- (`coordination.features`) or an inline use-case feature. A feature may+-- name the capabilities contributing to it through `capabilityRefs`; this+-- profile stores no list of features in return, and a capability a+-- feature still needs is an improvement request, not a planned capability. -- * Granularity: one capability is one thing a consumer can adopt AND verify -- independently. Where two candidates always ship together and are proven by -- the same evidence, they are one capability. A catalog with one record per@@ -159,7 +163,7 @@ in Profile::{ , name = "capabilities" , description = Some- "Consumer-facing catalog of what a repository provides today: stable CAP-N handles, an explicit compatibility promise, and evidence. Provision claims only — absent capabilities are improvement requests, and capabilities that span repositories are use-case features owned by the consumer. The house `reviews` family and OKF `verified` coexist: `reviews` records far more than `verified` can, so an approving `reviews` entry should also be mirrored into `verified` to keep the derived trust tier accurate."+ "Consumer-facing catalog of what a repository provides today: stable CAP-N handles, an explicit compatibility promise, and evidence. Provision claims only — absent capabilities are improvement requests, and behavior that spans repositories is a feature owned by the consumer, standalone or inline in a use case. The house `reviews` family and OKF `verified` coexist: `reviews` records far more than `verified` can, so an approving `reviews` entry should also be mirrored into `verified` to keep the derived trust tier accurate." , okfVersion = "0.2" , requireBundleVersion = Some "0.2" , allowUnknownTypes = False
+ test/fixtures/catalogue/profiles/coordination/designs-for.dhall view
@@ -0,0 +1,610 @@+--| Builder for the designs profile: design work for one surface (a page or a+-- self-contained part of one), split by the question each phase answers.+-- ADR-19 records the boundaries this profile draws.+--+-- `designs.dhall` applies this with no house conventions and is the export a+-- consumer normally selects. A house that wants a closed list of viewer axes+-- calls the builder itself:+--+-- Profiles.coordination.designsFor+-- { viewerAxisNames = [ "plan", "device" ] }+--+-- One folder per surface:+--+-- <surface>/brief Design Brief BRIEF-N why / what+-- <surface>/breadboard Breadboard BB-N how — structure+-- <surface>/design Detailed Design DES-N how — presentation+--+--+-- ## Implementation is not a phase here+--+-- The fourth phase, implementation, is tracked where the catalog already+-- tracks delivery: the `coordination.features` a breadboard's affordances name+-- (status, owners, acceptance criteria, delivery evidence), the work that+-- builds them, and the checks against the brief's success criteria. Storing an+-- `implemented` status here would copy delivery state that the features own+-- (ADR-17).+--+--+-- ## Jobs and features are referenced, never restated+--+-- Customer jobs and their outcomes are canonical `coordination.jobs` concepts.+-- A brief links them (`jobs[].jobRef`, `outcomeRefs`) and gives each a short+-- local name; it never restates the statement. Affordances name the+-- `coordination.features` that deliver them. Every reference into another+-- bundle is external only, with the target prefix fixed by+-- `externalUriPattern` (ADR-17).+--+-- A surface's own chain — `brief` on a breadboard, `breadboard` on a detailed+-- design — is local handles only (ADR-19). `supersededBy` may also name a+-- concept in another bundle, by canonical URI with the same prefix.+--+--+-- ## Viewers, subject states, axes and scenarios+--+-- The brief declares who looks (viewers), the states the thing shown can be in+-- (subject states), the switches that modulate either (viewer and subject+-- axes) and the scenarios the surface must work in, at least one per viewer ×+-- subject state. The breadboard says what each viewer gets. The detailed+-- design draws scenarios and says, in one line each, which drawn scenario an+-- undrawn one equals.+--+-- Viewer axes are the attributes every viewer has on every page of a product —+-- a plan, a role, a device — so every brief must answer each of them. Which+-- attributes those are is a house decision, so the names are open unless the+-- house closes them through `viewerAxisNames`; a closed list is what stops a+-- brief from misspelling one. Subject axes are page-specific and always open.+--+-- These cross-document invariants — viewer names declared by the brief,+-- affordances and places declared by the breadboard, scenario coverage — are+-- not expressible in a descriptor. The type guidance names them so an author+-- reads them, per ADR-18.+--+--+-- ## Decisions and questions are shared by a surface's documents+--+-- Decision ids (D-N) and question ids (Q-N) are unique across a surface's three+-- documents, so "D-18" means one thing in a commit message. Each entry lives in+-- the document whose phase it settles.+--+--+-- ## Not a spec language+--+-- This profile gives agents the context for the work. Conditions and+-- exceptions go in prose (`note`, `rules`, the body) rather than in new fields.+--+--+-- ## Presence classes and status+--+-- Each type declares a house `status` (Policy one in `Profile/V02.dhall`), so+-- none takes OKF's `status` or `stale_after`. `reviews` is the only+-- unconditional recommendation (ADR-8); decisions, open questions and subject+-- axes are optional, because a complete document can lack each of them, except+-- that a detailed design records its chosen direction in `decisions` from+-- `chosen` on. Approval moves a document to `validated` or `designed`;+-- `verified` is then required and mirrors the approving `reviews` entry+-- (ADR-4).+--+-- `legacyTimestamp` is deliberately absent: the profile is introduced at v0.2.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let Nested = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let House = { viewerAxisNames : List Text }++let scalar =+ \(name : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let nscalar =+ \(name : Text) ->+ \(description : Text) ->+ Nested::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let nlist =+ \(name : Text) ->+ \(description : Text) ->+ Nested::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.List+ }++let none = [] : List Nested.Type++let records =+ \(name : Text) ->+ \(description : Text) ->+ \(key : Optional Text) ->+ \(required : List Nested.Type) ->+ \(optional : List Nested.Type) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.List+ , elementFields = Some { required, recommended = none, optional }+ , uniqueBy = key+ }++let whenStatus =+ \(hasValue : List Text) -> Some { field = "status", hasValue }++-- A concept URI in some producer's bundle; the bundle name is the producer's+-- choice, so it is never hard-coded.+let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++-- okf requires a reference's `localPrefix` to be a prefix this profile+-- declares, so references into other bundles borrow one and forbid local+-- values (ADR-17).+let externalOnly =+ \(localPrefix : Text) ->+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix+ , externalUriSchemes = [ "mori" ]+ , allowLocal = False+ , externalUriPattern = Some (conceptUri prefix)+ }++-- A surface's three documents live in one folder and chain by local handle+-- (ADR-19), so `brief` and `breadboard` take no external URI.+let localOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = prefix+ , externalUriSchemes = [] : List Text+ , allowSelf = False+ }++-- A replacement can live in another bundle once a surface moves, so+-- `supersededBy` also takes a canonical concept URI with the same prefix.+let localOrConcept =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = prefix+ , externalUriSchemes = [ "mori" ]+ , allowSelf = False+ , externalUriPattern = Some (conceptUri prefix)+ }++let designId =+ \(prefix : Text) ->+ FieldRule::{+ , field = "designId"+ , description = Some "Bundle-scoped stable ${prefix}-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle prefix)+ }++let status =+ \(values : List Text) ->+ FieldRule::{+ , field = "status"+ , description = Some "Workflow step. See the type guidance."+ , allowedValues = values+ , cardinality = Cardinality.Scalar+ }++let supersededBy =+ \(prefix : Text) ->+ FieldRule::{+ , field = "supersededBy"+ , description = Some "Later document replacing this one."+ , cardinality = Cardinality.Scalar+ , reference = localOrConcept prefix+ , when = whenStatus [ "retired" ]+ }++let verifiedWhen =+ \(values : List Text) ->+ v02.verified+ // { description = Some "The approving review, mirrored from `reviews`."+ , when = whenStatus values+ }++-- Shared by all three types ------------------------------------------------++let decisions =+ records+ "decisions"+ "Settled questions a later phase must not reopen. D-N ids are unique across the surface's three documents."+ (Some "id")+ [ nscalar "id" "D-N handle."+ // { format = Some (FieldFormat.DocumentHandle "D") }+ , nscalar "statement" "The decision."+ , Nested::{+ , field = "date"+ , description = Some "When it was decided."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Date+ }+ ]+ [ nscalar "by" "Who decided." ]++let openQuestions =+ records+ "openQuestions"+ "Questions still open, each with an owner. Q-N ids are unique across the surface's three documents."+ (Some "id")+ [ nscalar "id" "Q-N handle."+ // { format = Some (FieldFormat.DocumentHandle "Q") }+ , nscalar "question" "The question."+ , nscalar "owner" "Who answers it."+ ]+ none++-- Design Brief: why / what ------------------------------------------------++let viewers =+ records+ "viewers"+ "The kinds of viewer this surface distinguishes, declared per brief. Every `viewer` value in this brief and in its breadboard must be one of these names."+ (Some "name")+ [ nscalar "name" "Short identifier used by jobs, scenarios and visibility."+ , nscalar "definition" "Who this is, in one sentence."+ ]+ [ nscalar "computedFrom" "How the app decides a viewer is this kind." ]++let jobs =+ records+ "jobs"+ "The canonical jobs this surface serves, each under a short local name, and which viewers hold it. Where they arrive from is the breadboard's `connections`."+ (Some "name")+ [ nscalar "name" "Short local name used by scenarios and connections."+ , nscalar "jobRef" "Canonical Mori URI of the JOB concept."+ // { reference = externalOnly "BRIEF" "JOB" }+ ]+ [ nlist "viewers" "Viewer names that hold this job." ]++let subjectStates =+ records+ "subjectStates"+ "The states the thing shown can be in, declared per brief — how much of a profile is filled in; open, shipped or cancelled for an order. Omit it when the subject has none, as on a form."+ (Some "name")+ [ nscalar "name" "Short identifier used by scenarios."+ , nscalar "definition" "What the subject has in this state, in facts that can be checked against the data, in one sentence."+ ]+ none++let outcomeRefs =+ records+ "outcomeRefs"+ "The job outcomes this surface is meant to move — the desired outcomes, owned by the jobs."+ (None Text)+ [ nscalar "jobRef" "Canonical Mori URI of a job in `jobs`."+ // { reference = externalOnly "BRIEF" "JOB" }+ , nscalar "outcomeName" "Name of an outcome declared by that job."+ ]+ none++let successCriteria =+ records+ "successCriteria"+ "How the shipped surface is judged: observable conditions, each with how it will be checked. Implementation verifies these."+ (Some "id")+ [ nscalar "id" "Brief-local SC-N handle."+ // { format = Some (FieldFormat.DocumentHandle "SC") }+ , nscalar "statement" "Observable condition that must hold."+ , nscalar "verification" "Procedure or evidence that will prove it."+ ]+ none++let applies =+ Nested::{+ , field = "applies"+ , description = Some+ "Whether this axis changes the surface. Words, not booleans: YAML would read yes/no as true/false."+ , allowedValues = [ "applies", "does-not-apply", "open" ]+ , cardinality = Cardinality.Scalar+ }++-- An empty `viewerAxisNames` leaves `name` open: okf treats an empty+-- `allowedValues` as no constraint.+let viewerAxes =+ \(house : House) ->+ records+ "viewerAxes"+ "Attributes of the viewer that every surface must consider, each answered explicitly: does it change this surface, and why. `does-not-apply` with a reason is a complete answer; silence is not."+ (Some "name")+ [ Nested::{+ , field = "name"+ , description = Some "Which viewer attribute."+ , allowedValues = house.viewerAxisNames+ , cardinality = Cardinality.Scalar+ }+ , applies+ , nscalar "because" "Why it applies or does not — the evidence, not the conclusion."+ ]+ [ nscalar "rules" "What changes when it applies, or which scenarios and open questions carry it." ]++let subjectAxes =+ records+ "subjectAxes"+ "Attributes of the thing being viewed, or of the context, that modulate this surface: independent switches that combine freely, unlike subject states. Page-specific, so names are open."+ (Some "name")+ [ nscalar "name" "Which attribute of the subject or context."+ , applies+ , nscalar "because" "Why it applies — the evidence."+ ]+ [ nscalar "rules" "What changes when it applies, or which scenarios and open questions carry it." ]++let scenarios =+ records+ "scenarios"+ "The situations the surface must work in, each with a concrete fixture: at least one per viewer × subject state, plus one for any axis that needs its own look. Later phases are checked against these."+ (Some "id")+ [ nscalar "id" "Short handle such as S1; stable once given."+ , nscalar "viewer" "A declared viewer name."+ , nscalar+ "fixture"+ "Who is looking, what data exists and what is missing, and the entry point — never what the page shows."+ ]+ [ nscalar "state" "A declared subject state."+ , nlist "axes" "Declared viewer and subject axes this scenario exercises."+ , nlist "jobs" "Local job names this scenario exercises."+ ]++let brief =+ \(house : House) ->+ TypeRule::{+ , type = "Design Brief"+ , description = Some+ "Why the surface exists and what it must achieve: the problem, who views it and for which jobs, the states the thing shown can be in, the outcomes it should move, the constraints, the scenarios it must work in, and the criteria it is judged by. No structure, no layout."+ , guidance = Some+ ''+ The body carries what only prose can: **Problem**, **Who it's for**, **Non-goals**, **Constraints** (cite decisions), principles, tone, and the baseline data that shows which subject state is the common one.++ okf checks the shapes, not the cross-references or the scenario coverage the `scenarios` description asks for. Read them: every `jobs[].viewers` and `scenarios[].viewer` is a declared viewer; every `scenarios[].state` a declared subject state; every `scenarios[].axes` entry a declared viewer or subject axis; every `scenarios[].jobs` entry a local job name; every `outcomeRefs[].jobRef` a `jobs[].jobRef`.++ `draft` → `validated` when the surface's approver signs off.+ ''+ , pathPattern = Some "*/brief"+ , idPrefix = Some "BRIEF"+ , frontmatter = FrontmatterRules::{+ , required =+ [ designId "BRIEF"+ , status [ "draft", "validated", "retired" ]+ , scalar "surface" "Route or component the surface is, e.g. /orders/[id]."+ , FieldRule::{+ , field = "origin"+ , description = Some "Mori project URI that owns the surface."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.UriWithScheme "mori")+ }+ , viewers+ , jobs+ , outcomeRefs // { when = whenStatus [ "validated" ] }+ , viewerAxes house // { when = whenStatus [ "validated" ] }+ , scenarios // { when = whenStatus [ "validated" ] }+ , successCriteria // { when = whenStatus [ "validated" ] }+ , verifiedWhen [ "validated" ]+ , supersededBy "BRIEF"+ ]+ , optional =+ [ subjectStates+ , subjectAxes+ , decisions+ , openQuestions+ , FieldRule::{+ , field = "useCases"+ , description = Some "Canonical Mori URIs of the use cases this surface realises."+ , cardinality = Cardinality.List+ , reference = externalOnly "BRIEF" "UC"+ }+ ]+ }+ }++-- Breadboard: how — structure -------------------------------------------++let places =+ records+ "places"+ "Where the viewer can be: the surface's own places and the external places that lead into it or that it leads to."+ (Some "name")+ [ nscalar "name" "Stable place name."+ , Nested::{+ , field = "kind"+ , description = Some+ "`surface` for a place this breadboard designs; `external` for one it only connects to."+ , allowedValues = [ "surface", "external" ]+ , cardinality = Cardinality.Scalar+ }+ , nscalar "description" "What the place is, in one sentence."+ ]+ none++let affordances =+ records+ "affordances"+ "What a viewer can read or do in each surface place, in words, with no layout. Each names the features that deliver it."+ (Some "name")+ [ nscalar "name" "Stable affordance name, used by visibility, connections and the detailed design."+ , nscalar "place" "A declared surface place."+ , nscalar "does" "What the viewer reads or does with it."+ ]+ [ Nested::{+ , field = "features"+ , description = Some "Canonical Mori URIs of the FEAT concepts that deliver it."+ , cardinality = Cardinality.List+ , reference = externalOnly "BB" "FEAT"+ }+ ]++let connections =+ records+ "connections"+ "How the viewer moves: from an external place or an affordance to a place. Connections from external places are the entry points and name the jobs that arrive by them."+ (None Text)+ [ nscalar "from" "A declared place or affordance."+ , nscalar "to" "A declared place."+ ]+ [ nscalar "carries" "Context passed along, such as a selected tab or filter in the URL."+ , nlist "jobs" "Brief job names that arrive by this connection."+ ]++let visibility =+ FieldRule::{+ , field = "visibility"+ , description = Some+ "Affordance × viewer, one entry each. `shown` and `hidden` are what they say; `collapsed` is present but de-emphasised (relevance, not permission); `filtered` shows the affordance with its items filtered per viewer; `prompts` turns an empty affordance into a call to fill it in. A condition or exception goes in `note`. The owner is a row like any other."+ , cardinality = Cardinality.List+ , elementFields = Some+ { required =+ [ nscalar "affordance" "A declared affordance name."+ , nscalar "viewer" "A viewer declared by the brief."+ , Nested::{+ , field = "rule"+ , description = Some "What this viewer gets."+ , allowedValues = [ "shown", "collapsed", "hidden", "filtered", "prompts" ]+ , cardinality = Cardinality.Scalar+ }+ ]+ , recommended = none+ , optional =+ [ nscalar "note" "Why, or the condition under which the rule changes."+ ]+ }+ }++let breadboard =+ TypeRule::{+ , type = "Breadboard"+ , description = Some+ "How the surface works, before what it looks like: places, the affordances in each, the connections between them, and what each viewer gets."+ , guidance = Some+ ''+ The body carries **ordering rules** written over the brief's viewers, subject states and axes — for example, which affordance comes first for each viewer — not a priority table per scenario.++ okf checks the shapes, not the cross-references. Read them: every `viewer` is declared by the brief, and every `connections[].jobs` entry is a brief job name; every `affordances[].place` is a declared `surface` place; every `visibility[].affordance`, `connections[].from` and `connections[].to` is declared here.++ `draft` → `validated` when the surface's approver signs off on the structure.+ ''+ , pathPattern = Some "*/breadboard"+ , idPrefix = Some "BB"+ , frontmatter = FrontmatterRules::{+ , required =+ [ designId "BB"+ , status [ "draft", "validated", "retired" ]+ , scalar "brief" "The brief this breadboard answers."+ // { reference = localOnly "BRIEF" }+ , places+ , affordances+ , connections+ , visibility+ , verifiedWhen [ "validated" ]+ , supersededBy "BB"+ ]+ , optional = [ decisions, openQuestions ]+ }+ }++-- Detailed Design: how — presentation ------------------------------------++let states =+ records+ "states"+ "The states each affordance is designed in: with-data, and empty wherever the affordance can be empty; add absent (feature not shipped) and any domain state."+ (Some "affordance")+ [ nscalar "affordance" "A breadboard affordance."+ , nlist "states" "State names; every one appears on at least one final screen."+ ]+ none++let designArtifacts =+ records+ "designArtifacts"+ "Where the design lives."+ (Some "resource")+ [ nscalar "kind" "Tool or medium, such as figma, sketch, screenshot or video."+ , nscalar "resource" "A URL, or a path relative to this concept for a file in the repository."+ ]+ [ nscalar "note" "What this artifact shows." ]++let detailedDesign =+ TypeRule::{+ , type = "Detailed Design"+ , description = Some+ "What the breadboard looks like and how it behaves: the chosen direction, layouts, visual hierarchy, components, interaction details and every affordance state, with the final screens."+ , guidance = Some+ ''+ `exploring` (rough ideas, then options) → `chosen` (the direction is recorded in `decisions`) → `designed` (the final screens are exported, the `## Design handoff` section is written and the design review is signed off).++ By `designed`, every brief scenario is either in the `## Design handoff` table, which names the scenario of each screen, or has a line in the body saying which drawn scenario it equals and what differs, and every `states[].affordance` is declared by the breadboard. okf does not check either; read them.+ ''+ , pathPattern = Some "*/design"+ , idPrefix = Some "DES"+ , frontmatter = FrontmatterRules::{+ , required =+ [ designId "DES"+ , status [ "exploring", "chosen", "designed", "retired" ]+ , scalar "breadboard" "The breadboard this design presents."+ // { reference = localOnly "BB" }+ , states+ , designArtifacts // { when = whenStatus [ "chosen", "designed" ] }+ , decisions // { when = whenStatus [ "chosen", "designed" ] }+ , verifiedWhen [ "designed" ]+ , supersededBy "DES"+ ]+ , optional = [ openQuestions ]+ }+ }++in \(house : House) ->+ Profile::{+ , name = "designs"+ , description = Some+ "Design work per surface in three linked concepts — Design Brief (why/what), Breadboard (structure), Detailed Design (presentation). Jobs and features are canonical concepts referenced by Mori URI; implementation is tracked on the features. Approval requires `verified`, mirroring the approving `reviews` entry."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "designId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar "type" "Design Brief, Breadboard or Detailed Design."+ , scalar "title" "Human-readable title, usually the surface."+ , scalar "description" "One sentence: what this document settles."+ , v02.generated+ // { description = Some "§5.2. Who produced this document's current content, and when." }+ ]+ , recommended = [ reviewRule ]+ , optional =+ [ FieldRule::{ field = "tags", cardinality = Cardinality.List }+ , v02.verified+ // { description = Some "§5.2. Mirror an approving `reviews` entry here." }+ ]+ }+ , types = [ brief house, breadboard, detailedDesign ]+ }
+ test/fixtures/catalogue/profiles/coordination/designs.dhall view
@@ -0,0 +1,5 @@+--| Design work per surface — Design Brief, Breadboard and Detailed Design —+-- with no house conventions: viewer-axis names are open. See+-- `./designs-for.dhall` for the profile itself and for how a house closes the+-- viewer-axis names.+./designs-for.dhall { viewerAxisNames = [] : List Text }
+ test/fixtures/catalogue/profiles/coordination/features.dhall view
@@ -0,0 +1,300 @@+--| Profile for independently addressable product features with stable FEAT-N handles.+--+-- ## What a feature is+--+-- A feature is one coherent product behavior that can advance from discovery+-- to delivery and can appear in more than one use case. Its `FEAT-N` handle+-- does not depend on its title, its owning repository, or any screen layout,+-- so a use case, a request, a bug, and a future product-management UI can all+-- name it stably.+--+-- It sits beside the rest of the coordination family without replacing any of+-- it:+--+-- * a use case is a scenario; it names the features it relies on through+-- `featureRefs` and stores no copy of their delivery state;+-- * a capability is a producer's claim about behavior that already exists.+-- `capabilityRefs` names capabilities contributing to this feature. That+-- is not a claim that they deliver all of it, and missing producer behavior+-- is an improvement request, never a planned capability;+-- * a specification governs, or is proposed to govern, the feature's+-- contract. `specificationRefs` names it; a linked draft specification is+-- not a ratified obligation, and delivery does not prove conformance;+-- * an improvement request is repository work. `improvementRequests` names+-- requests delivering parts of this feature, and a request may point back+-- through its own `origin`. Neither direction is required.+--+-- No inverse lists are stored: where a feature is used, which bugs affect it,+-- and which requests originate from it are derived from the other side.+--+--+-- ## Acceptance criteria are scoped to their feature+--+-- `acceptanceCriteria` reuses the improvement-request shape: record-local+-- `AC-N` ids with a `statement` and a `verification`. A criterion is+-- identified by its owning feature URI plus its id, so a feature's `AC-1` and+-- a request's `AC-1` are unrelated, and satisfying one never satisfies the+-- other. `verification` names the expected procedure or evidence; it is not a+-- claim that the check already passed.+--+--+-- ## Delivery is not outcome+--+-- `deliveredAt` and `deliveryEvidence` show that the behavior was implemented+-- and accepted. They do not show that a customer job is better served; that is+-- a product-research question, and a delivered feature never changes a job's+-- `validationStatus`.+--+--+-- ## References to other profiles are external only+--+-- okf requires a reference rule's `localPrefix` to be an `idPrefix` this+-- profile declares, so every reference uses `FEAT` with `allowLocal = False`+-- and constrains its target family through `externalUriPattern`. Declaring a+-- foreign prefix such as `CAP` here would be a profile load failure. `owners`+-- is a list of Mori project URIs: a format, not a concept reference, so it+-- carries no reference rule. Whether a referenced concept exists and has the+-- right type needs the other bundle; the opt-in+-- `scripts/check-product-discovery.py` gate checks it until Mori does.+--+--+-- ## Presence classes+--+-- Nothing is recommended (ADR-8). A `discovered` feature may have no owners and+-- no criteria; both become required once a feature is `planned`,+-- `in-progress`, `blocked`, or `delivered`. A technical enabling feature may+-- name no job, and must not invent one to fill `jobRefs`. `priority` is a+-- producer's declared ordering signal for a UI, never a computed score.+--+-- No `guidance` is declared (ADR-12).+--+--+-- ## `legacyTimestamp` is deliberately absent+--+-- This profile is introduced at v0.2 and no v0.1 feature corpus exists.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let scalar =+ \(name : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let nestedScalar =+ \(name : Text) ->+ \(description : Text) ->+ NestedFieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++-- `FEAT` is the only prefix this profile declares, so it is the `localPrefix`+-- of every reference, all of which name concepts in other bundles.+let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "FEAT"+ , externalUriSchemes = [ "mori" ]+ , allowLocal = False+ , externalUriPattern = Some (conceptUri prefix)+ }++let referenceList =+ \(name : Text) ->+ \(prefix : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.List+ , reference = externalOnly prefix+ }++let whenStatus =+ \(hasValue : List Text) -> Some { field = "status", hasValue }++let committed = whenStatus [ "planned", "in-progress", "blocked", "delivered" ]++let acceptanceCriteria =+ FieldRule::{+ , field = "acceptanceCriteria"+ , description = Some+ "Feature-local product acceptance conditions with AC-N ids; not request criteria."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ nestedScalar+ "id"+ "Stable feature-local AC-N handle for this condition."+ // { format = Some (FieldFormat.DocumentHandle "AC") }+ , nestedScalar+ "statement"+ "Observable product condition that must hold for acceptance."+ , nestedScalar+ "verification"+ "Expected procedure or evidence that will prove the condition."+ ]+ }+ , uniqueBy = Some "id"+ , when = committed+ }++let outcomeRefs =+ FieldRule::{+ , field = "outcomeRefs"+ , description = Some+ "Named outcomes of jobs in `jobRefs` that this feature is meant to move."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ nestedScalar "jobRef" "Canonical Mori URI of a job in `jobRefs`."+ // { reference = externalOnly "JOB" }+ , nestedScalar "outcomeName" "Name of an outcome declared by that job."+ ]+ }+ }++in Profile::{+ , name = "features"+ , description = Some+ "Independently addressable product features with stable FEAT handles: customer-visible behavior, a discovery-to-delivery lifecycle, owners and feature-local AC-N acceptance criteria once committed, delivery evidence, and canonical links to jobs, capabilities, specifications, and improvement requests. Delivery is not proof of outcome or conformance."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "featureId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar "type" "The Feature concept type."+ , scalar "title" "Short, stable-sounding feature name."+ , scalar "description" "One sentence stating the feature."+ , v02.generated+ // { description = Some+ "§5.2. Who produced this feature's current content, and when."+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ // { description = Some+ "§5.2. Independent confirmations that this document is accurate. Mirror an approving `reviews` entry here."+ }+ ]+ }+ , -- `status` is a house delivery vocabulary (ADR-1); this profile does not+ -- splice OKF v0.2 §5.4's `status` or `stale_after`.+ types =+ [ TypeRule::{+ , type = "Feature"+ , description = Some+ "A coherent product behavior with its own identity and delivery lifecycle."+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "featureId"+ , description = Some "Bundle-scoped stable FEAT-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "FEAT")+ }+ , scalar+ "status"+ "Delivery state: discovered, planned, in-progress, delivered, blocked, deferred, or retired."+ // { allowedValues =+ [ "discovered"+ , "planned"+ , "in-progress"+ , "delivered"+ , "blocked"+ , "deferred"+ , "retired"+ ]+ }+ , scalar+ "behavior"+ "Proposed or actual customer-visible behavior."+ , FieldRule::{+ , field = "owners"+ , description = Some+ "Mori project URIs accountable for delivering the feature."+ , cardinality = Cardinality.List+ , format = Some (FieldFormat.UriWithScheme "mori")+ , when = committed+ }+ , acceptanceCriteria+ , scalar+ "deliveredAt"+ "UTC RFC3339 time the behavior was delivered and accepted."+ // { format = Some FieldFormat.Rfc3339Utc+ , when = whenStatus [ "delivered" ]+ }+ , v02.sources+ // { field = "deliveryEvidence"+ , description = Some+ "Evidence that the behavior was delivered, in the shape of §5.1 sources."+ , when = whenStatus [ "delivered" ]+ }+ ]+ , optional =+ [ referenceList+ "jobRefs"+ "JOB"+ "Canonical Mori URIs of the customer jobs this feature serves."+ , outcomeRefs+ , referenceList+ "capabilityRefs"+ "CAP"+ "Canonical Mori URIs of producer capabilities contributing behavior."+ , referenceList+ "specificationRefs"+ "SPEC"+ "Canonical Mori URIs of specifications governing this feature."+ , referenceList+ "improvementRequests"+ "IR"+ "Canonical Mori URIs of requests delivering parts of this feature."+ , scalar+ "priority"+ "Producer-declared ordering signal: low, medium, high, or critical."+ // { allowedValues = [ "low", "medium", "high", "critical" ] }+ ]+ }+ , pathPattern = Some "*"+ , idPrefix = Some "FEAT"+ }+ ]+ }
+ test/fixtures/catalogue/profiles/coordination/friction-log.dhall view
@@ -0,0 +1,293 @@+--| Profile for a friction log: the cost of doing work in a repository, recorded+-- before it is known to be a defect or a request.+--+-- ## What a friction entry is, and what it is not+--+-- A friction entry records that a surface — a document, an interface, a tool,+-- an environment, or a house process — cost an implementer material effort+-- while doing planned work. It states what was attempted, what happened+-- instead, what it cost, and what the implementer did about it. It makes no+-- claim about who is wrong, which is exactly what separates it from its two+-- coordination siblings:+--+-- * A bug report is a *broken provision claim*. Friction becomes a bug+-- report once the recording repository can say which published claim is+-- contradicted and how to reproduce it. Until then it stays friction.+-- * An improvement request is a *gap* a producer should close. Friction+-- becomes a request once someone can state the outcome they want.+--+-- Friction is therefore the intake queue in front of both: raw, first-person,+-- and cheap to write, so that a surface's owner can see across every consuming+-- repository where it costs the most before any of it has been triaged.+-- `status` records the triage outcome and `promotedTo` names the artifact that+-- took over.+--+-- A plan's Surprises & Discoveries section is a different thing again: it+-- holds findings that changed the plan or explain a non-obvious result.+-- Friction that also changed a plan is recorded in both places, each citing+-- the other.+--+-- Granularity: one entry is one surface costing effort once. The same surface+-- costing effort again under another plan is a second entry, because+-- recurrence is the signal this family exists to surface. `duplicateOf` is for+-- two entries describing the same occurrence, not the same cause.+--+--+-- ## `impact` is an observable consequence, not a mood+--+-- The four values are ordered by what happened to the work, and are assigned+-- by observation:+--+-- * `blocked` — the planned path could not proceed; a human or a change+-- outside the repository was needed.+-- * `detour` — the work proceeded only through a workaround, which the+-- entry must name.+-- * `slowdown` — the intended path worked, but cost material time through+-- retries, re-reading, or trial and error.+-- * `nuisance` — no material cost; recorded because it will recur.+--+-- Priority is deliberately absent, for the reason `bug-reports` gives: it+-- changes with the schedule and has no place in a cross-repository record.+--+--+-- ## `kind` names the surface, not the cause+--+-- `kind` says what sort of thing cost the effort, so a surface's owner can+-- filter a corpus to what they own. It is not a root cause: a `documentation`+-- entry may turn out to be an interface defect, and promotion is where that+-- is recorded.+--+--+-- ## `origin` and `affects`+--+-- `origin` is the Mori URI of the project in which the friction was+-- experienced. `affects` is the URI of the project or artifact whose surface+-- cost the effort. They differ in the case this family exists for: a consumer+-- recording friction with a dependency. `affects` admits any absolute URI+-- rather than only `mori://`, because friction is often with a tool a house+-- does not register; a registered subject is always named by its canonical+-- Mori URI, and a house that registers everything it depends on may narrow+-- the format in its local descriptor.+--+--+-- ## Presence classes+--+-- Nothing is recommended (ADR-8). `workaround` is demanded once `impact` is+-- `detour`, because a detour is defined by having one. `promotedTo`,+-- `duplicateOf` and `resolution` are demanded by the status that gives each+-- its meaning.+--+-- `reviews` and `verified` are optional rather than recommended, which+-- departs from the other coordination profiles on purpose. An entry is a+-- first-person observation that is correct the moment it is written, and+-- demanding review provenance on every entry under `--strict` would price the+-- log out of the habit it exists to create. Triage, not review, is this+-- family's quality gate, and it is recorded in `status`.+--+-- `amended` records later changes that leave the entry standing — a status+-- change at triage — so `generated` keeps who observed the friction, as+-- `documentation.architectureDecisions` does for a decision.+--+--+-- ## The house `status` vocabulary+--+-- Per ADR-1 this profile declares a house lifecycle vocabulary on `status` and+-- therefore does NOT splice OKF v0.2 §5.4's `status` or §5.5's `stale_after`.+--+--+-- ## `legacyTimestamp` is deliberately absent+--+-- This profile is introduced at v0.2 and no v0.1 friction corpus exists.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let Nested = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let condition =+ \(field : Text) -> \(hasValue : List Text) -> { field, hasValue }++let scalar =+ \(name : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++-- One mapping per later change, newest last. `by` and `at` take the+-- `generated` formats so the two families read the same way.+let amended =+ FieldRule::{+ , field = "amended"+ , description = Some+ "Later changes that leave the entry standing, such as a status change at triage, newest last. `generated` keeps who observed the friction."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ Nested::{+ , field = "by"+ , description = Some+ "§7. The actor responsible: `<producer>/<version>`, `human:<id>`, or `process:<id>`."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Actor+ }+ , Nested::{+ , field = "at"+ , description = Some+ "UTC RFC3339 timestamp, ending in `Z`, for when this happened."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , Nested::{+ , field = "change"+ , description = Some "What changed, in one sentence."+ , cardinality = Cardinality.Scalar+ }+ ]+ }+ }++in Profile::{+ , name = "friction-log"+ , description = Some+ "The cost of doing work in a repository, recorded before it is known to be a defect or a request: one entry per surface that cost an implementer effort, with a stable FRIC handle, an impact graded by what happened to the work, the kind of surface, who felt it and whose surface it was, and a triage status naming the artifact that took it over. Behavior known to be wrong is a bug report; a gap someone can state is an improvement request."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "frictionId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar "type" "The Friction Entry concept type."+ , scalar "title" "Short statement of what cost effort."+ , scalar+ "description"+ "One sentence naming the surface and the cost, evaluable without the body."+ , v02.generated+ // { description = Some+ "§5.2. Who observed the friction and wrote this entry, and when."+ }+ , FieldRule::{+ , field = "frictionId"+ , description = Some "Bundle-scoped stable FRIC-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "FRIC")+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Where triage has got to."+ , allowedValues =+ [ "logged", "promoted", "resolved", "accepted", "duplicate" ]+ , cardinality = Cardinality.Scalar+ }+ , -- Consequence, not priority. Take the most severe value that actually+ -- occurred; see the header for what each one means.+ FieldRule::{+ , field = "impact"+ , description = Some+ "What happened to the work, most severe first."+ , allowedValues = [ "blocked", "detour", "slowdown", "nuisance" ]+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "kind"+ , description = Some "The sort of surface that cost the effort."+ , allowedValues =+ [ "documentation", "interface", "tooling", "environment", "process" ]+ , cardinality = Cardinality.Scalar+ }+ , scalar+ "origin"+ "Mori URI of the project in which the friction was experienced."+ // { format = Some (FieldFormat.UriWithScheme "mori") }+ , -- Any absolute URI, not only `mori://`: see the header.+ scalar+ "affects"+ "URI of the project or artifact whose surface cost the effort: its canonical Mori URI when registered, otherwise its canonical URL."+ // { format = Some FieldFormat.Uri }+ , -- The next four are conditionally required, which okf spells+ -- `required` plus `when`: a `when` condition gates a presence demand,+ -- and an `optional` rule makes no demand to gate. Each stays checked+ -- whenever it is present under any other value.+ scalar+ "workaround"+ "What the implementer did instead. Demanded once `impact` is `detour`."+ // { when = Some (condition "impact" [ "detour" ]) }+ , scalar+ "promotedTo"+ "URI of the bug report, improvement request, plan, or external issue that now tracks this friction. Demanded once `status` is `promoted`."+ // { format = Some FieldFormat.Uri+ , when = Some (condition "status" [ "promoted" ])+ }+ , FieldRule::{+ , field = "duplicateOf"+ , description = Some+ "The entry this one duplicates, as a local FRIC-N handle or an external Mori URI. Demanded once `status` is `duplicate`."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "FRIC"+ , externalUriSchemes = [ "mori" ]+ }+ , when = Some (condition "status" [ "duplicate" ])+ }+ , scalar+ "resolution"+ "How the cause went away, or why the house lives with it. Demanded once `status` is `resolved` or `accepted`."+ // { when = Some (condition "status" [ "resolved", "accepted" ]) }+ ]+ , optional =+ [ scalar+ "affectedVersion"+ "Released version of the affecting surface the friction was met with; `unreleased` or `unknown` otherwise."+ , scalar+ "plan"+ "Repository-relative path or Mori URI of the plan being implemented when the friction occurred."+ , FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , amended+ , reviewRule+ , v02.verified+ // { description = Some+ "§5.2. Independent confirmations that this entry is accurate. A list of mappings, or one bare mapping."+ }+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Friction Entry"+ , description = Some+ "One surface costing an implementer effort once, recorded before triage."+ , -- Two sentences, each one of ADR-18's kinds: the first is lifecycle+ -- meaning and who signs it off; the second is where body prose goes.+ guidance = Some+ ''+ An entry is `logged` when written and moves only at triage by the recording repository: to `promoted` once the artifact in `promotedTo` tracks it, to `resolved` once its cause is gone, to `accepted` once the house decides to live with it, or to `duplicate`.+ What was attempted and what happened instead, with evidence, go in the body under `## Attempted` and `## Encountered`.+ ''+ , pathPattern = Some "*"+ , idPrefix = Some "FRIC"+ }+ ]+ }
+ test/fixtures/catalogue/profiles/coordination/jobs.dhall view
@@ -0,0 +1,287 @@+--| Profile for canonical customer jobs with stable JOB-N handles.+--+-- ## What a job is+--+-- A job, in the Jobs-to-be-Done sense, is the progress a person is trying to+-- make in a particular situation, stated independently of any product, screen,+-- or implementation. A job stays `active` when one attempted solution fails:+-- the failure is evidence about the solution, not about whether the customer+-- still wants to make progress.+--+-- A use case may still carry an inline `jobs` record describing one scenario's+-- actor and circumstances. That record is a scenario snapshot; a `Job` concept+-- is the canonical statement the snapshot links to through `jobs[].jobRef`.+--+--+-- ## Research support is a declaration, not a computation+--+-- `validationStatus` says what the authors currently believe about whether+-- research supports this statement of the job: `untested`, `supported`,+-- `mixed`, or `challenged`. Anything other than `untested` demands an+-- `assessment` naming the product-research findings it synthesises and the+-- reasoning that connects them. Support is never derived by counting finding+-- stances, and it is never inferred from `verified`, which confirms that the+-- document is accurate rather than that the job is real.+--+--+-- ## Outcomes may be qualitative+--+-- Each outcome needs only a `name` and a `description`. The measurement+-- members (`metric`, `unit`, `direction`, `method`, `baseline`, `target`,+-- `population`, `period`) are optional because many jobs are understood+-- before anyone can measure them. okf cannot express "all of these or none of+-- these" between nested members, nor a general finite-number format, so the+-- opt-in `scripts/check-product-discovery.py` gate enforces that a measured+-- outcome names its metric, unit, direction, and method together, and that+-- `baseline` and `target` are finite numbers. A `target` is an aspiration and+-- never an observation; an observed value belongs in a finding's+-- `measurements`.+--+--+-- ## References to other profiles are external only+--+-- `assessment.findingRefs` names product-research findings, which live in a+-- bundle governed by `documentation.productResearch`. okf requires a reference+-- rule's `localPrefix` to be an `idPrefix` this profile declares, so the rule+-- uses `JOB` with `allowLocal = False` and constrains the target family with+-- `externalUriPattern`. Declaring a foreign `PR` prefix here would be a+-- profile load failure. Whether a cited finding exists, is a finding rather+-- than a study, and actually assesses this job is a cross-bundle question that+-- okf leaves to Mori; until Mori resolves it, the opt-in gate does.+--+--+-- ## Presence classes+--+-- Nothing is recommended. Per ADR-8 a field is recommended only when a+-- well-run corpus carries it, and an untested draft job routinely has no+-- review, no sources, and no assessment. `reviews` is optional for the same+-- reason `documentation.specifications` makes it optional: a draft nobody has+-- reviewed yet is complete for what it is.+--+-- No `guidance` is declared (ADR-12): there is no observed authoring failure+-- to correct yet.+--+--+-- ## `legacyTimestamp` is deliberately absent+--+-- This profile is introduced at v0.2 and no v0.1 job corpus exists.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let scalar =+ \(name : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let nestedScalar =+ \(name : Text) ->+ \(description : Text) ->+ NestedFieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++-- A concept URI in some producer's bundle; the bundle name is the producer's+-- choice, so it is never hard-coded.+let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++-- `JOB` is the only prefix this profile declares, so it is the `localPrefix`+-- of every reference, including ones that may never be local.+let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "JOB"+ , externalUriSchemes = [ "mori" ]+ , allowLocal = False+ , externalUriPattern = Some (conceptUri prefix)+ }++let outcomes =+ FieldRule::{+ , field = "outcomes"+ , description = Some+ "Desired results that show the job's progress, qualitative or measured."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ nestedScalar "name" "Stable outcome name, unique within this job."+ , nestedScalar+ "description"+ "What the actor would observe when this outcome is achieved."+ ]+ , optional =+ [ nestedScalar "metric" "What is measured to track this outcome."+ , nestedScalar "unit" "Unit every baseline, target, and measurement uses."+ , nestedScalar+ "direction"+ "Which way the metric should move: `increase`, `decrease`, or `maintain`."+ // { allowedValues = [ "increase", "decrease", "maintain" ] }+ , nestedScalar "method" "How the metric is obtained."+ , nestedScalar+ "baseline"+ "Measured starting value in `unit`, documented by a `sources` entry."+ , nestedScalar+ "target"+ "Aspired value in `unit`; never an observation."+ , nestedScalar "population" "Whose behavior the baseline describes."+ , nestedScalar "period" "When the baseline was observed."+ ]+ }+ , uniqueBy = Some "name"+ }++let assessment =+ FieldRule::{+ , field = "assessment"+ , description = Some+ "The authors' synthesis of research findings behind `validationStatus`."+ , objectFields = Some NestedRules::{+ , required =+ [ nestedScalar+ "rationale"+ "Why the cited findings justify the declared validation status."+ , nestedScalar+ "assessedAt"+ "UTC RFC3339 time the assessment was made."+ // { format = Some FieldFormat.Rfc3339Utc }+ , NestedFieldRule::{+ , field = "findingRefs"+ , description = Some+ "Canonical Mori URIs of the product-research findings assessed."+ , cardinality = Cardinality.List+ , reference = externalOnly "PR"+ }+ ]+ }+ , when = Some+ { field = "validationStatus"+ , hasValue = [ "supported", "mixed", "challenged" ]+ }+ }++in Profile::{+ , name = "jobs"+ , description = Some+ "Canonical Jobs-to-be-Done statements with stable JOB handles: the progress an actor seeks in a situation, qualitative or measured outcomes, and an explicit research assessment citing product-research findings. States customer progress, never a solution."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "jobId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar "type" "The Job concept type."+ , scalar "title" "Short name for the progress the actor seeks."+ , scalar "description" "One sentence stating the job."+ , v02.generated+ // { description = Some+ "§5.2. Who produced this job's current content, and when."+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ // { description = Some+ "§5.2. Independent confirmations that this document is accurate. Mirror an approving `reviews` entry here."+ }+ ]+ }+ , -- `status` and `validationStatus` are house vocabularies (ADR-1); this+ -- profile does not splice OKF v0.2 §5.4's `status` or `stale_after`. See+ -- the header of ../../Profile/V02.dhall.+ types =+ [ TypeRule::{+ , type = "Job"+ , description = Some+ "Progress an actor seeks in a situation, independent of any solution."+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "jobId"+ , description = Some "Bundle-scoped stable JOB-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "JOB")+ }+ , scalar "actor" "Person or role trying to make progress."+ , scalar "situation" "Circumstance in which the job arises."+ , scalar "motivation" "Progress the actor wants to make."+ , scalar+ "status"+ "Lifecycle of this job statement: `draft`, `active`, or `retired`."+ // { allowedValues = [ "draft", "active", "retired" ] }+ , scalar+ "validationStatus"+ "Declared research support: `untested`, `supported`, `mixed`, or `challenged`."+ // { allowedValues =+ [ "untested", "supported", "mixed", "challenged" ]+ }+ , outcomes+ , assessment+ ]+ , optional =+ [ scalar+ "functionalContext"+ "Practical task constraints surrounding the job."+ , scalar+ "emotionalContext"+ "How the actor wants to feel, or avoid feeling."+ , scalar+ "socialContext"+ "How the actor wants to be perceived by others."+ , FieldRule::{+ , field = "relatedJobs"+ , description = Some+ "Related local JOB handles or canonical Mori job URIs."+ , cardinality = Cardinality.List+ , reference = Some HandleReferenceRule::{+ , localPrefix = "JOB"+ , externalUriSchemes = [ "mori" ]+ , allowSelf = False+ , allowLocal = True+ , externalUriPattern = Some (conceptUri "JOB")+ }+ }+ , v02.sources+ // { description = Some+ "§5.1. Evidence this job statement and any baseline were derived from."+ }+ ]+ }+ , pathPattern = Some "*"+ , idPrefix = Some "JOB"+ }+ ]+ }
+ test/fixtures/catalogue/profiles/coordination/launches.dhall view
@@ -0,0 +1,292 @@+--| Launches use the feature acceptance shape for readiness and the shared sources+-- shape for launch evidence. A planned date never proves publication or outcome.+-- Landing Pages link designs rather than duplicating layout. House lifecycles+-- replace OKF status/staleAfter; drafts need no review or evidence, so nothing is+-- recommended. All foreign references are canonical and external-only; LAUNCH and+-- LP references may be local within this profile. No legacyTimestamp exists.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "LAUNCH"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++in Profile::{+ , name = "launches"+ , description = Some+ "Launches coordinate go-live events; Landing Pages name the surfaces and their evidence."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "launchId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Launch"+ , description = Some+ "Coordinated go-live work with a deadline and explicit readiness conditions."+ , idPrefix = Some "LAUNCH"+ , pathPattern = Some "launches/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "launchId"+ , description = Some "Bundle-scoped stable LAUNCH-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "LAUNCH")+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Launch coordination lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "planned", "ready", "launched", "cancelled" ]+ }+ , FieldRule::{+ , field = "liveBy"+ , description = Some "Intended UTC go-live deadline."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , FieldRule::{+ , field = "readiness"+ , description = Some+ "Conditions for release and how each will be verified; not a pass declaration."+ , cardinality = Cardinality.List+ , when = Some+ { field = "status", hasValue = [ "ready", "launched" ] }+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "id"+ , description = Some "Launch-local AC-N condition handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "AC")+ }+ , NestedFieldRule::{+ , field = "statement"+ , description = Some "Observable readiness condition."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "verification"+ , description = Some+ "Procedure or evidence that will verify the condition."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ , uniqueBy = Some "id"+ }+ , FieldRule::{+ , field = "launchedAt"+ , description = Some "Actual UTC launch time."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ , when = Some { field = "status", hasValue = [ "launched" ] }+ }+ , v02.sources+ // { field = "launchEvidence"+ , description = Some+ "Evidence of the actual launch, in the shared sources shape."+ , when = Some { field = "status", hasValue = [ "launched" ] }+ }+ , FieldRule::{+ , field = "cancellationReason"+ , description = Some "Why the launch was cancelled."+ , cardinality = Cardinality.Scalar+ , when = Some { field = "status", hasValue = [ "cancelled" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "featureRefs"+ , description = Some "Features in the launch."+ , cardinality = Cardinality.List+ , reference = externalOnly "FEAT"+ }+ , FieldRule::{+ , field = "segmentRefs"+ , description = Some "Segments addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "campaignRefs"+ , description = Some "Launch campaigns."+ , cardinality = Cardinality.List+ , reference = externalOnly "CMP"+ }+ , FieldRule::{+ , field = "landingPageRefs"+ , description = Some "Related Landing Pages."+ , cardinality = Cardinality.List+ , reference = Some HandleReferenceRule::{+ , localPrefix = "LP"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "LP")+ }+ }+ , FieldRule::{+ , field = "initiativeRef"+ , description = Some "Initiative coordinating the launch."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "INIT"+ }+ , FieldRule::{+ , field = "chapters"+ , description = Some "Producer chapter labels in scope."+ , cardinality = Cardinality.List+ }+ ]+ }+ , guidance = Some+ "Readiness describes conditions and verification, not that they passed. Launched records a go-live event and evidence, not proof of customer outcome."+ }+ , TypeRule::{+ , type = "Landing Page"+ , description = Some+ "A published or planned GTM surface; design work stays in the designs profile."+ , idPrefix = Some "LP"+ , pathPattern = Some "landing-pages/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "launchId"+ , description = Some "Bundle-scoped stable LP-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "LP")+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Landing-page publication lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "draft", "live", "retired" ]+ }+ , FieldRule::{+ , field = "slug"+ , description = Some "Producer-owned page route."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "url"+ , description = Some "Published page URL."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Uri+ , when = Some { field = "status", hasValue = [ "live" ] }+ }+ , FieldRule::{+ , field = "liveAt"+ , description = Some "Actual UTC publication time."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ , when = Some { field = "status", hasValue = [ "live" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "designRefs"+ , description = Some+ "Design Brief or Detailed Design URIs for the surface."+ , cardinality = Cardinality.List+ , reference = externalOnly "(BRIEF|DES)"+ }+ , FieldRule::{+ , field = "segmentRefs"+ , description = Some "Segments the page addresses."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "featureRefs"+ , description = Some "Product behavior the page explains."+ , cardinality = Cardinality.List+ , reference = externalOnly "FEAT"+ }+ , FieldRule::{+ , field = "initiativeRef"+ , description = Some "Initiative coordinating the page."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "INIT"+ }+ , FieldRule::{+ , field = "launchRef"+ , description = Some "Related Launch."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "LAUNCH"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "LAUNCH")+ }+ }+ , FieldRule::{+ , field = "claimRefs"+ , description = Some "Claim Checks for factual copy."+ , cardinality = Cardinality.List+ , reference = externalOnly "CLM"+ }+ ]+ }+ }+ ]+ }
test/fixtures/catalogue/profiles/coordination/package.dhall view
@@ -6,11 +6,42 @@ -- claimed but does not hold. Behavior that was never provided is an improvement -- request rather than a bug, which is the line that keeps the two apart. --+-- Jobs and features give the consumer corner canonical identities. A job+-- states the progress a customer seeks, independent of any solution; a feature+-- is one product behavior with its own delivery lifecycle, reusable across use+-- cases, and linked to the jobs it serves, the capabilities contributing to it,+-- the specifications governing it, and the requests delivering it. A use case+-- remains the scenario that ties them together. The research behind a job's+-- support lives in `documentation.productResearch`.+--+-- Designs carry a surface from why to how before it is built: a Design Brief+-- names the jobs it serves and the scenarios it must work in, a Breadboard+-- names the features its affordances rely on, and a Detailed Design presents+-- it. Implementation stays on those features. `designsFor` builds the profile+-- with a house's closed list of viewer axes; it is a function, not a profile.+--+-- A friction log sits in front of the triangle: an entry records that a+-- surface cost an implementer effort before anyone can say whether it is a+-- defect, a gap, or neither, and its triage status names the bug report or+-- improvement request that took it over.+-- -- A pattern application sits beside them: a service's decision about whether -- an assessable catalog pattern governs it and which of its own checks prove it.-{ bugReports = ./bug-reports.dhall+-- Campaigns coordinate work; audiences describe its consumer populations.+-- Strategy names intended outcomes; launches coordinate publication.+{ strategy = ./strategy.dhall+, launches = ./launches.dhall+, campaigns = ./campaigns.dhall+, audiences = ./audiences.dhall+, bugReports = ./bug-reports.dhall , capabilities = ./capabilities.dhall+, designs = ./designs.dhall+, designsFor = ./designs-for.dhall+, features = ./features.dhall+, frictionLog = ./friction-log.dhall , improvementRequests = ./improvement-requests.dhall+, jobs = ./jobs.dhall , patternApplications = ./pattern-applications.dhall+, transitions = ./transitions.dhall , useCases = ./use-cases.dhall }
+ test/fixtures/catalogue/profiles/coordination/strategy.dhall view
@@ -0,0 +1,440 @@+--| Strategy coordinates intended outcomes and work, beside campaigns and features.+-- Key results reuse job measurement members and store aspirations, not observed+-- current values. Assessments cite Findings or Claim Checks. Strategic Theme is+-- separate from Use Case Theme. No engineering-project, pricing or competitor type+-- is introduced. House status replaces OKF status/staleAfter; no legacyTimestamp.+-- Nothing is recommended: untested drafts legitimately lack reviews and evidence.+-- References crossing profiles are canonical and external-only; local references+-- join the three strategy types. check-gtm.py owns unsupported cross-record checks.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "THEME"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++let hypothesis = ../../Profile/Hypothesis.dhall++in Profile::{+ , name = "strategy"+ , description = Some+ "Strategic Themes group time-bounded Objectives and coordinated Initiatives."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "strategyId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Strategic Theme"+ , description = Some+ "A persistent strategic direction grouping objectives and initiatives."+ , idPrefix = Some "THEME"+ , pathPattern = Some "themes/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "strategyId"+ , description = Some "Bundle-scoped stable THEME-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "THEME")+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Strategic theme lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "active", "retired" ]+ }+ ]+ , optional = [] : List FieldRule.Type+ }+ }+ , TypeRule::{+ , type = "Objective"+ , description = Some+ "A time-bounded intended outcome with explicit key-result targets."+ , idPrefix = Some "OBJ"+ , pathPattern = Some "objectives/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "strategyId"+ , description = Some "Bundle-scoped stable OBJ-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "OBJ")+ }+ , FieldRule::{+ , field = "periodStart"+ , description = Some "UTC start of the objective period."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , FieldRule::{+ , field = "periodEnd"+ , description = Some "UTC end of the objective period."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Objective commitment and result lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "draft", "committed", "achieved", "missed", "dropped" ]+ }+ , FieldRule::{+ , field = "keyResults"+ , description = Some+ "Objective-local targets; observed values belong in cited evidence."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "id"+ , description = Some "Objective-local KR-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "KR")+ }+ , NestedFieldRule::{+ , field = "metric"+ , description = Some "What is measured."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "unit"+ , description = Some "Unit of the target."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "direction"+ , description = Some "Desired direction of change."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "increase", "decrease", "maintain" ]+ }+ , NestedFieldRule::{+ , field = "target"+ , description = Some+ "Finite numeric aspiration, never an observed value."+ , cardinality = Cardinality.Scalar+ }+ ]+ , optional =+ [ NestedFieldRule::{+ , field = "method"+ , description = Some "How the result will be measured."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "baseline"+ , description = Some "Finite numeric starting point."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "population"+ , description = Some "Population the target describes."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "jobRef"+ , description = Some+ "Canonical job URI for an outcome pair."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "JOB"+ }+ , NestedFieldRule::{+ , field = "outcomeName"+ , description = Some+ "Name of an outcome on the referenced job."+ , cardinality = Cardinality.Scalar+ }+ ]+ }+ , uniqueBy = Some "id"+ }+ , FieldRule::{+ , field = "assessment"+ , description = Some+ "Evidence-based assessment of the objective result."+ , cardinality = Cardinality.Any+ , when = Some+ { field = "status", hasValue = [ "achieved", "missed" ] }+ , objectFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "rationale"+ , description = Some+ "Why the evidence justifies the declared result."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "assessedAt"+ , description = Some "UTC assessment time."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , NestedFieldRule::{+ , field = "evidenceRefs"+ , description = Some+ "Canonical Finding or Claim Check URIs."+ , cardinality = Cardinality.List+ , reference = externalOnly "(PR|CLM)"+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "dropReason"+ , description = Some "Why the objective was dropped."+ , cardinality = Cardinality.Scalar+ , when = Some { field = "status", hasValue = [ "dropped" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "owners"+ , description = Some "Accountable actors."+ , cardinality = Cardinality.List+ , format = Some FieldFormat.Actor+ }+ , FieldRule::{+ , field = "themeRef"+ , description = Some "Strategic Theme for this objective."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "THEME"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "THEME")+ }+ }+ ]+ }+ , guidance = Some+ "Achieved or missed is an author assessment of cited evidence, never inferred from delivery. The opt-in check-gtm.py checks numeric targets, ordered periods and key-result outcome pairs and resolves assessment evidence to Findings or Claim Checks."+ }+ , TypeRule::{+ , type = "Initiative"+ , description = Some+ "Coordinated product, growth, marketing or operations work; not an engineering implementation project."+ , idPrefix = Some "INIT"+ , pathPattern = Some "initiatives/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "strategyId"+ , description = Some "Bundle-scoped stable INIT-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "INIT")+ }+ , FieldRule::{+ , field = "kind"+ , description = Some "Kind of coordinated work."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "product", "growth", "marketing", "operations" ]+ }+ , FieldRule::{+ , field = "status"+ , description = Some+ "Initiative commitment and execution lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues =+ [ "proposed", "committed", "active", "done", "killed" ]+ }+ , FieldRule::{+ , field = "themeRef"+ , description = Some "Strategic Theme guiding this initiative."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "THEME"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "THEME")+ }+ }+ , FieldRule::{+ , field = "killedReason"+ , description = Some "Why the initiative was killed."+ , cardinality = Cardinality.Scalar+ , when = Some { field = "status", hasValue = [ "killed" ] }+ }+ , FieldRule::{+ , field = "outcome"+ , description = Some+ "What resulted from the completed initiative."+ , cardinality = Cardinality.Scalar+ , when = Some { field = "status", hasValue = [ "done" ] }+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "objectiveRefs"+ , description = Some "Objectives this initiative serves."+ , cardinality = Cardinality.List+ , reference = Some HandleReferenceRule::{+ , localPrefix = "OBJ"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "OBJ")+ }+ }+ , FieldRule::{+ , field = "keyResultRefs"+ , description = Some+ "Objective-local key results this initiative serves."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "objectiveRef"+ , description = Some "Objective holding the key result."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "OBJ"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "OBJ")+ }+ }+ , NestedFieldRule::{+ , field = "krId"+ , description = Some "Key-result id on that objective."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "KR")+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , hypothesis+ , FieldRule::{+ , field = "studyRef"+ , description = Some "Canonical experiment Study URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "PR"+ }+ , FieldRule::{+ , field = "owners"+ , description = Some "Accountable actors."+ , cardinality = Cardinality.List+ , format = Some FieldFormat.Actor+ }+ , FieldRule::{+ , field = "featureRefs"+ , description = Some "Features contributing product behavior."+ , cardinality = Cardinality.List+ , reference = externalOnly "FEAT"+ }+ , FieldRule::{+ , field = "campaignRefs"+ , description = Some+ "Campaigns contributing coordinated GTM work."+ , cardinality = Cardinality.List+ , reference = externalOnly "CMP"+ }+ , FieldRule::{+ , field = "segmentRefs"+ , description = Some "Audience Segments served."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "launchRefs"+ , description = Some "Related launches."+ , cardinality = Cardinality.List+ , reference = externalOnly "LAUNCH"+ }+ , FieldRule::{+ , field = "dependsOn"+ , description = Some "Initiatives that must precede this work."+ , cardinality = Cardinality.List+ , reference = Some HandleReferenceRule::{+ , localPrefix = "INIT"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "INIT")+ }+ }+ , FieldRule::{+ , field = "parentInitiativeRef"+ , description = Some "Broader Initiative containing this work."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "INIT"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "INIT")+ }+ }+ , FieldRule::{+ , field = "wave"+ , description = Some "Producer-defined sequencing wave."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "size"+ , description = Some "Producer-defined size estimate."+ , cardinality = Cardinality.Scalar+ }+ ]+ }+ , guidance = Some+ "The opt-in check-gtm.py resolves objective-local key-result ids and requires studyRef to name an experiment Study; initiative delivery does not establish customer outcome."+ }+ ]+ }
+ test/fixtures/catalogue/profiles/coordination/transitions.dhall view
@@ -0,0 +1,349 @@+-- Declared intent only. Observation envelopes and retirement verdicts belong to Mori.+let Profile = ../../Profile/Type.dhall++let Frontmatter = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let F = okf.defaults.FieldRule++let N = okf.defaults.NestedFieldRule++let R = okf.defaults.NestedRules++let C = okf.Cardinality++let Format = okf.FieldFormat++let v02 = ../../Profile/V02.dhall++let reviews = ../../Profile/ReviewRule.dhall++in Profile::{+ , name = "transitions"+ , description = Some+ "Declared platform responsibility movement with TR-N handles; phase describes intent, never proves predecessor retirement."+ , guidance = Some+ "Use canonical project roots and typed capability/context/flow/use-case URIs. Requirements may be absent or empty. Their environments default to all; acceptance defaults false; itemized defaults true for consumer-migration, event-obligation and responsibility-transfer, otherwise false. Responsibility-transfer is always itemized and has no responsibility field. Mori validates lowercase slugs, fixed maxAge durations, requirements[].owner role/party (a depth-two object), exactly one successor identity, to iff move, cross-list keys, retained duties, environment subsets, registry ownership and reference kinds. The profile engine cannot express these joins, forbidden conditional fields, regexes or recursive owner records. Capability status can raise caution but supplies no retirement evidence. Run mori transitions validate after strict profile enforcement."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "transitionId"+ , frontmatter = Frontmatter::{+ , required =+ [ F::{+ , field = "type"+ , description = Some "Transition concept type."+ , cardinality = C.Scalar+ }+ , F::{+ , field = "title"+ , description = Some "Transition name."+ , cardinality = C.Scalar+ }+ , F::{+ , field = "description"+ , description = Some "One-sentence intent."+ , cardinality = C.Scalar+ }+ , v02.generated+ ]+ , recommended = [ reviews ]+ , optional = [ v02.legacyTimestamp ]+ }+ , types =+ [ TypeRule::{+ , type = "Transition"+ , pathPattern = Some "*"+ , idPrefix = Some "TR"+ , frontmatter = Frontmatter::{+ , required =+ [ F::{+ , field = "transitionId"+ , description = Some "Positive unpadded TR-N handle."+ , cardinality = C.Scalar+ , format = Some (Format.DocumentHandle "TR")+ }+ , F::{+ , field = "coordinator"+ , description = Some "Owning coordinator project root."+ , cardinality = C.Scalar+ , format = Some (Format.UriWithScheme "mori")+ }+ , F::{+ , field = "kind"+ , cardinality = C.Scalar+ , allowedValues =+ [ "split"+ , "absorption"+ , "replacement"+ , "consolidation"+ , "extraction"+ ]+ }+ , F::{+ , field = "phase"+ , cardinality = C.Scalar+ , allowedValues =+ [ "planned"+ , "in-progress"+ , "validating"+ , "complete"+ , "abandoned"+ ]+ }+ , F::{+ , field = "environments"+ , cardinality = C.List+ , description = Some "Nonempty lowercase environment slugs."+ }+ , F::{+ , field = "accountable"+ , description = Some "Accountable parties."+ , cardinality = C.List+ , elementFields = Some R::{+ , required =+ [ N::{+ , field = "role"+ , description = Some "Accountable role."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "party"+ , description = Some+ "Owner identity; unassigned is explicit uncertainty."+ , cardinality = C.Scalar+ }+ ]+ , optional = [] : List N.Type+ }+ }+ , F::{+ , field = "predecessors"+ , description = Some+ "Services participating before responsibility movement."+ , cardinality = C.List+ , elementFields = Some R::{+ , required =+ [ N::{+ , field = "project"+ , description = Some "Canonical predecessor project root."+ , cardinality = C.Scalar+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "disposition"+ , description = Some "Retire or retain."+ , cardinality = C.Scalar+ , allowedValues = [ "retire", "retain" ]+ }+ ]+ , optional = [] : List N.Type+ }+ , uniqueBy = Some "project"+ }+ , F::{+ , field = "successors"+ , description = Some+ "Known project or explicitly pending identity; exactly one is checked by Mori."+ , cardinality = C.List+ , elementFields = Some R::{+ , required =+ [ N::{+ , field = "key"+ , description = Some "Unique lowercase successor key."+ , cardinality = C.Scalar+ }+ ]+ , optional =+ [ N::{+ , field = "project"+ , description = Some "Canonical successor project root."+ , cardinality = C.Scalar+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "pendingIdentity"+ , description = Some+ "Unsettled successor identity; no invented project."+ , cardinality = C.Scalar+ }+ ]+ }+ , uniqueBy = Some "key"+ }+ , F::{+ , field = "responsibilities"+ , description = Some "Declared duties and their dispositions."+ , cardinality = C.List+ , elementFields = Some R::{+ , required =+ [ N::{+ , field = "key"+ , description = Some "Unique lowercase responsibility key."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "title"+ , description = Some "Human-readable duty."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "from"+ , description = Some "Listed predecessor project root."+ , cardinality = C.Scalar+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "disposition"+ , description = Some "Move, discontinue or retain."+ , cardinality = C.Scalar+ , allowedValues = [ "move", "discontinue", "retain" ]+ }+ , N::{+ , field = "to"+ , description = Some+ "Listed successor key, required for move."+ , cardinality = C.Scalar+ , when = Some+ { field = "disposition", hasValue = [ "move" ] }+ }+ ]+ , optional =+ [ N::{+ , field = "owner"+ , description = Some "Responsible party."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "capabilityRefs"+ , description = Some "Capability concept URIs; read only."+ , cardinality = C.List+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "contextRefs"+ , description = Some "Typed DDD context URIs."+ , cardinality = C.List+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "flowRefs"+ , description = Some "Typed DDD flow URIs."+ , cardinality = C.List+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "useCaseRefs"+ , description = Some "Use Case concept URIs."+ , cardinality = C.List+ , format = Some (Format.UriWithScheme "mori")+ }+ ]+ }+ , uniqueBy = Some "key"+ }+ ]+ , optional =+ [ F::{+ , field = "phaseSource"+ , description = Some "Source for the declared phase."+ , cardinality = C.Scalar+ }+ , F::{+ , field = "requirements"+ , description = Some+ "Optional retirement requirements; absence never establishes readiness. Mori validates each owner object."+ , cardinality = C.List+ , elementFields = Some R::{+ , required =+ [ N::{+ , field = "key"+ , description = Some "Unique lowercase requirement key."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "predecessor"+ , description = Some "Listed predecessor project root."+ , cardinality = C.Scalar+ , format = Some (Format.UriWithScheme "mori")+ }+ , N::{+ , field = "kind"+ , description = Some "Obligation category."+ , cardinality = C.Scalar+ , allowedValues =+ [ "consumer-migration"+ , "responsibility-transfer"+ , "routing-cutover"+ , "in-flight-drain"+ , "data-obligation"+ , "event-obligation"+ , "rollback"+ , "operational-continuity"+ , "ownership"+ , "other"+ ]+ }+ , N::{+ , field = "statement"+ , description = Some "What must be established."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "minimumBasis"+ , description = Some "Minimum factual evidence strength."+ , cardinality = C.Scalar+ , allowedValues =+ [ "declared", "source-observed", "runtime-observed" ]+ }+ ]+ , optional =+ [ N::{+ , field = "owner"+ , description = Some+ "Required object with role and party by the Mori gate; nested mappings exceed the profile engine depth."+ , cardinality = C.Any+ }+ , N::{+ , field = "environments"+ , description = Some+ "Subset of transition environments; defaults to all."+ , cardinality = C.List+ }+ , N::{+ , field = "requiresAcceptance"+ , description = Some "Defaults to false."+ , cardinality = C.Scalar+ , format = Some Format.Boolean+ }+ , N::{+ , field = "itemized"+ , description = Some+ "Defaults true for consumer-migration, event-obligation and responsibility-transfer; otherwise false."+ , cardinality = C.Scalar+ , format = Some Format.Boolean+ }+ , N::{+ , field = "responsibility"+ , description = Some+ "Optional listed duty key; forbidden for responsibility-transfer."+ , cardinality = C.Scalar+ }+ , N::{+ , field = "maxAge"+ , description = Some+ "Owner-selected fixed duration; Mori parses and checks it."+ , cardinality = C.Scalar+ }+ ]+ }+ , uniqueBy = Some "key"+ }+ ]+ }+ }+ ]+ }
test/fixtures/catalogue/profiles/coordination/use-cases.dhall view
@@ -1,4 +1,36 @@ --| Profile for JTBD use cases connected to repository-owned feature work.+--+-- ## Inline records and canonical references+--+-- A use case is a scenario. Its inline `jobs` and `features` records predate+-- the standalone `coordination.jobs` and `coordination.features` profiles and+-- remain valid unchanged. A use case may now link to the canonical concepts:+--+-- * `jobs[].jobRef` names the canonical job an inline job record is a+-- scenario snapshot of, and `jobs[].outcomeNames` names that job's+-- outcomes this scenario is about. The inline record keeps describing this+-- scenario's actor and circumstances; it does not replace the job;+-- * `featureRefs` names standalone features this scenario relies on, and is+-- the preferred representation for new use cases, which then omit inline+-- `features`;+-- * `features[].featureRef` links a retained inline record to a standalone+-- feature during migration. The standalone feature owns the current+-- status, owners, and acceptance; the inline record is a snapshot a UI+-- must not write back.+--+-- Inline `features` is therefore optional rather than required: an early+-- `draft` or `validated` scenario can rely on no feature yet. That a+-- `planned`, `in-progress`, or `delivered` use case names at least one inline+-- feature or `featureRefs` entry is an any-of presence condition okf cannot+-- express, so profile validation checks only the shape of what is present and+-- the opt-in `scripts/check-product-discovery.py` gate enforces readiness,+-- together with name uniqueness, `features[].jobs` resolution, and duplicate+-- canonical references. Existing corpora are not required to run it.+--+-- All three references name concepts in other bundles. okf requires a+-- reference rule's `localPrefix` to be a prefix this profile declares, so they+-- use `UC` with `allowLocal = False` and a JOB-N or FEAT-N+-- `externalUriPattern`. let Profile = ../../Profile/Type.dhall let FrontmatterRules = ../../Profile/FrontmatterRules.dhall@@ -39,6 +71,20 @@ , cardinality = Cardinality.Scalar } +let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "UC"+ , externalUriSchemes = [ "mori" ]+ , allowLocal = False+ , externalUriPattern = Some (conceptUri prefix)+ }+ let moriList = \(name : Text) -> \(description : Text) ->@@ -64,7 +110,18 @@ , nestedScalar "outcome" "Observable result that satisfies the job." ] , recommended = [] : List NestedFieldRule.Type- , optional = [] : List NestedFieldRule.Type+ , optional =+ [ nestedScalar+ "jobRef"+ "Canonical Mori URI of the job this record is a scenario snapshot of."+ // { reference = externalOnly "JOB" }+ , NestedFieldRule::{+ , field = "outcomeNames"+ , description = Some+ "Names of outcomes on the `jobRef` job that this scenario concerns."+ , cardinality = Cardinality.List+ }+ ] } } @@ -104,6 +161,10 @@ , moriList "improvementRequests" "Stable Mori concept URIs of repository-owned requests delivering this feature."+ , nestedScalar+ "featureRef"+ "Canonical Mori URI of the standalone feature this record snapshots."+ // { reference = externalOnly "FEAT" } ] } }@@ -193,7 +254,6 @@ , format = Some (FieldFormat.UriWithScheme "mori") } , jobs- , features ] , recommended = [ FieldRule::{@@ -204,6 +264,14 @@ ] , optional = [ FieldRule::{+ , field = "featureRefs"+ , description = Some+ "Canonical Mori URIs of standalone features; preferred over inline `features`."+ , cardinality = Cardinality.List+ , reference = externalOnly "FEAT"+ }+ , features+ , FieldRule::{ , field = "improvementRequests" , description = Some "Stable Mori request URIs; mirror the union of feature-level request references."
test/fixtures/catalogue/profiles/documentation/architecture-decisions.dhall view
@@ -11,12 +11,50 @@ let HandleReferenceRule = okf.defaults.HandleReferenceRule +let Nested = okf.defaults.NestedFieldRule+ let Cardinality = okf.Cardinality let FieldFormat = okf.FieldFormat let v02 = ../../Profile/V02.dhall +-- `generated` is one mapping (§5.2): who produced the content. A later change+-- that leaves the decision standing, such as marking it superseded, is recorded+-- here instead, so the original author is not overwritten. Rewriting the+-- decision itself updates `generated`.+let amended =+ FieldRule::{+ , field = "amended"+ , description = Some+ "Later changes that leave the decision standing, such as marking it superseded, newest last. `generated` keeps who wrote the decision."+ , cardinality = Cardinality.List+ , elementFields = Some+ { required =+ [ Nested::{+ , field = "by"+ , description = Some+ "§7. The actor responsible: `<producer>/<version>`, `human:<id>`, or `process:<id>`."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Actor+ }+ , Nested::{+ , field = "at"+ , description = Some "UTC RFC3339 timestamp, ending in `Z`, for when this happened."+ , cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ , Nested::{+ , field = "change"+ , description = Some "What changed, in one sentence."+ , cardinality = Cardinality.Scalar+ }+ ]+ , recommended = [] : List Nested.Type+ , optional = [] : List Nested.Type+ }+ }+ let scalar = \(name : Text) -> \(description : Text) ->@@ -80,6 +118,7 @@ , scalar "originatingPlan" "Plan that produced the decision, when recorded."+ , amended , v02.verified // { description = Some "§5.2. Independent confirmations that this decision record is accurate."
+ test/fixtures/catalogue/profiles/documentation/messaging.dhall view
@@ -0,0 +1,275 @@+--| Messaging is documentation: an explanation to a customer, not another feature,+-- research finding or measurement record. Every factual proof point links a Claim+-- Check or Finding. Audience-any-of and target-type rules run in check-gtm.py.+-- House status replaces OKF status/staleAfter; nothing is recommended because a+-- draft may be unreviewed. Foreign references are canonical and external-only;+-- Positioning references may be local. Matrix coordinates are flat scalar labels.+-- No pricing/offer, competitor or engineering-project type is introduced.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "POS"+ , allowLocal = False+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri prefix)+ }++in Profile::{+ , name = "messaging"+ , description = Some+ "Positioning and Messages explain value to audiences with attributable proof points."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "messagingId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "type"+ , description = Some "The declared concept type."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "title"+ , description = Some "Human-readable concept title."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "description"+ , description = Some "One sentence describing the concept."+ , cardinality = Cardinality.Scalar+ }+ , v02.generated+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ , v02.sources+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Positioning"+ , description = Some+ "An explanation of differentiated value to a defined audience."+ , idPrefix = Some "POS"+ , pathPattern = Some "positioning/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "messagingId"+ , description = Some "Bundle-scoped stable POS-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "POS")+ }+ , FieldRule::{+ , field = "category"+ , description = Some "Category in which the offer is understood."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "problem"+ , description = Some "Customer problem addressed."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "differentiation"+ , description = Some+ "How this approach differs from alternatives."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Positioning lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "draft", "active", "retired" ]+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "segmentRefs"+ , description = Some "Audience Segments addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "personaRefs"+ , description = Some "Personas addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "PERSONA"+ }+ , FieldRule::{+ , field = "jobRefs"+ , description = Some "Customer jobs addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "JOB"+ }+ , FieldRule::{+ , field = "alternatives"+ , description = Some "Alternatives the audience considers."+ , cardinality = Cardinality.List+ }+ , FieldRule::{+ , field = "proofPoints"+ , description = Some "Factual support for value explanations."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "statement"+ , description = Some "The exact point being supported."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "evidenceRef"+ , description = Some "Canonical Finding or Claim Check URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "(PR|CLM)"+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ ]+ }+ , guidance = Some+ "The opt-in check-gtm.py requires an audience and resolves each proof point to a Finding or Claim Check; a Study is not a finding."+ }+ , TypeRule::{+ , type = "Message"+ , description = Some+ "Content addressed to a customer, relying on cited facts rather than declaring new ones."+ , idPrefix = Some "MSG"+ , pathPattern = Some "messages/*"+ , frontmatter = FrontmatterRules::{+ , required =+ [ FieldRule::{+ , field = "messagingId"+ , description = Some "Bundle-scoped stable MSG-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "MSG")+ }+ , FieldRule::{+ , field = "desiredAction"+ , description = Some "Action the audience is invited to take."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "status"+ , description = Some "Message preparation and use lifecycle."+ , cardinality = Cardinality.Scalar+ , allowedValues = [ "draft", "approved", "in-use", "retired" ]+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "segmentRefs"+ , description = Some "Audience Segments addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "SEG"+ }+ , FieldRule::{+ , field = "personaRefs"+ , description = Some "Personas addressed."+ , cardinality = Cardinality.List+ , reference = externalOnly "PERSONA"+ }+ , FieldRule::{+ , field = "hook"+ , description = Some "Customer-facing value hook."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "jobRef"+ , description = Some "Canonical job addressed."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "JOB"+ }+ , FieldRule::{+ , field = "positioningRef"+ , description = Some "Positioning this message expresses."+ , cardinality = Cardinality.Scalar+ , reference = Some HandleReferenceRule::{+ , localPrefix = "POS"+ , externalUriSchemes = [ "mori" ]+ , externalUriPattern = Some (conceptUri "POS")+ }+ }+ , FieldRule::{+ , field = "proofPoints"+ , description = Some "Factual support for value explanations."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ NestedFieldRule::{+ , field = "statement"+ , description = Some "The exact point being supported."+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{+ , field = "evidenceRef"+ , description = Some "Canonical Finding or Claim Check URI."+ , cardinality = Cardinality.Scalar+ , reference = externalOnly "(PR|CLM)"+ }+ ]+ , optional = [] : List NestedFieldRule.Type+ }+ }+ , FieldRule::{+ , field = "channel"+ , description = Some "Producer-owned delivery channel."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "matrixRow"+ , description = Some "Source matrix row identifier."+ , cardinality = Cardinality.Scalar+ }+ , FieldRule::{+ , field = "matrixDimensions"+ , description = Some+ "Source coordinates retained as scalar labels."+ , cardinality = Cardinality.List+ }+ ]+ }+ , guidance = Some+ "The opt-in check-gtm.py requires an audience and resolves proof-point evidence; approval is an accountable review decision, not inferred from validation."+ }+ ]+ }
test/fixtures/catalogue/profiles/documentation/package.dhall view
@@ -1,6 +1,10 @@ --| Reusable documentation profiles.-{ architectureDecisions = ./architecture-decisions.dhall+-- Messaging explains value to an audience with attributable evidence.+{ messaging = ./messaging.dhall+, architectureDecisions = ./architecture-decisions.dhall , patternCatalog = ./pattern-catalog.dhall+, productResearch = ./product-research.dhall+, runbooks = ./runbooks.dhall , researchDocuments = ./research-documents.dhall , specifications = ./specifications.dhall , terminology = ./terminology.dhall
+ test/fixtures/catalogue/profiles/documentation/product-research.dhall view
@@ -0,0 +1,336 @@+--| Profile for customer and product discovery research with stable PR-N handles.+--+-- ## Product research is not technical research+--+-- `documentation.researchDocuments` records technical investigations about+-- building software: which library, which runtime, which design. This profile+-- records what was learned about customers: who was studied, how, what was+-- observed, and what the authors concluded from it. The two have different+-- lifecycles and different handles (`RES-N` and `PR-N`), and neither is+-- converted into the other.+--+--+-- ## Studies and findings are separate concepts sharing one namespace+--+-- A `Product Research Study` is a bounded research activity: a question, a+-- method, a population, and a context. A `Product Research Finding` is one+-- separately citable result of a study. A finding keeps `observation` (what was+-- seen) apart from `interpretation` (the conclusion drawn), so a reader can+-- disagree with the second without disputing the first.+--+-- Both types use `productResearchId` and the `PR` prefix, so one bundle-wide+-- allocator issues their handles while `type` keeps them distinguishable;+-- `documentation.specifications` is the precedent for two types sharing one+-- prefix. A study stores no list of its findings: that inverse is derived from+-- each finding's `studyRef`.+--+--+-- ## What a finding claims about a job+--+-- `jobAssessments` records whether this finding `supports`, `challenges`, or is+-- `inconclusive` about one named claim concerning one canonical job. It does+-- not decide the job's overall `validationStatus`; the job's own `assessment`+-- does that, citing findings explicitly. `measurements` records observed values+-- of a job's measurable outcome, in that outcome's unit.+--+-- okf can check each member's shape but cannot check that a `value` is a finite+-- number, that `periodStart` precedes `periodEnd`, or that `studyRef` names a+-- study rather than another finding. The opt-in+-- `scripts/check-product-discovery.py` gate checks those, and everything else+-- that needs another concept, until okf and Mori own them.+--+--+-- ## References to other profiles are external only+--+-- Jobs live in a bundle governed by `coordination.jobs`. okf requires a+-- reference rule's `localPrefix` to be an `idPrefix` this profile declares, so+-- job references use `PR` with `allowLocal = False` and a JOB-N+-- `externalUriPattern`. `studyRef` and `supersededBy` may be local, because+-- studies and findings normally share a bundle.+--+--+-- ## Presence classes+--+-- Nothing is recommended (ADR-8): a planned study has no findings, no+-- sources, and no review. `reviews` is optional, as in+-- `documentation.specifications`. Raw material such as interview recordings+-- may live outside the bundle and be cited through `sources`; do not copy+-- sensitive transcripts into a concept.+--+-- No `guidance` is declared (ADR-12).+--+--+-- ## `legacyTimestamp` is deliberately absent+--+-- This profile is introduced at v0.2 and no v0.1 product-research corpus+-- exists.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let NestedRules = okf.defaults.NestedRules++let NestedFieldRule = okf.defaults.NestedFieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let reviewRule = ../../Profile/ReviewRule.dhall++let v02 = ../../Profile/V02.dhall++let scalar =+ \(name : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let nestedScalar =+ \(name : Text) ->+ \(description : Text) ->+ NestedFieldRule::{+ , field = name+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let conceptUri =+ \(prefix : Text) ->+ "mori://[^/]+/[^/]+/okf/[^/]+/concepts/${prefix}-[1-9][0-9]*"++-- `PR` is the only prefix this profile declares, so it is the `localPrefix` of+-- every reference, including the job references that are never local.+let externalOnly =+ \(prefix : Text) ->+ Some+ HandleReferenceRule::{+ , localPrefix = "PR"+ , externalUriSchemes = [ "mori" ]+ , allowLocal = False+ , externalUriPattern = Some (conceptUri prefix)+ }++let localOrExternalPR =+ Some+ HandleReferenceRule::{+ , localPrefix = "PR"+ , externalUriSchemes = [ "mori" ]+ , allowSelf = False+ , allowLocal = True+ , externalUriPattern = Some (conceptUri "PR")+ }++let rfc3339 =+ \(name : Text) ->+ \(description : Text) ->+ scalar name description // { format = Some FieldFormat.Rfc3339Utc }++let nestedRfc3339 =+ \(name : Text) ->+ \(description : Text) ->+ nestedScalar name description+ // { format = Some FieldFormat.Rfc3339Utc }++let productResearchId =+ FieldRule::{+ , field = "productResearchId"+ , description = Some "Bundle-scoped stable PR-N handle."+ , cardinality = Cardinality.Scalar+ , format = Some (FieldFormat.DocumentHandle "PR")+ }++let jobRef =+ nestedScalar "jobRef" "Canonical Mori URI of the job concerned."+ // { reference = externalOnly "JOB" }++let whenStatus =+ \(hasValue : List Text) -> Some { field = "status", hasValue }++let study =+ TypeRule::{+ , type = "Product Research Study"+ , description = Some+ "A bounded customer research activity with a question, method, and population."+ , frontmatter = FrontmatterRules::{+ , required =+ [ productResearchId+ , scalar "question" "What the study set out to learn."+ , scalar+ "method"+ "How evidence was gathered; explain `mixed` in the body."+ // { allowedValues =+ [ "interview"+ , "observation"+ , "survey"+ , "experiment"+ , "analytics"+ , "desk-research"+ , "mixed"+ ]+ }+ , scalar "population" "Who was studied, and how they were selected."+ , scalar "context" "Circumstances in which the study took place."+ , scalar+ "status"+ "Study lifecycle: `planned`, `active`, `complete`, or `cancelled`."+ // { allowedValues = [ "planned", "active", "complete", "cancelled" ] }+ , rfc3339 "completedAt" "UTC RFC3339 time the study completed."+ // { when = whenStatus [ "complete" ] }+ , scalar+ "limitations"+ "What the study cannot support, such as sampling or method limits."+ // { when = whenStatus [ "complete" ] }+ , scalar "cancellationReason" "Why the study stopped before completing."+ // { when = whenStatus [ "cancelled" ] }+ ]+ , optional =+ [ FieldRule::{+ , field = "jobRefs"+ , description = Some "Canonical Mori URIs of the jobs this study investigates."+ , cardinality = Cardinality.List+ , reference = externalOnly "JOB"+ }+ , rfc3339 "startedAt" "UTC RFC3339 time the study began."+ , v02.sources+ // { description = Some+ "§5.1. Protocols, recruiting material, or prior work the study drew on."+ }+ ]+ }+ , pathPattern = Some "*"+ , idPrefix = Some "PR"+ }++let jobAssessments =+ FieldRule::{+ , field = "jobAssessments"+ , description = Some+ "What this finding says about named claims concerning canonical jobs."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ jobRef+ , nestedScalar "claim" "The specific claim about the job being assessed."+ , nestedScalar+ "stance"+ "`supports`, `challenges`, or `inconclusive` toward the claim."+ // { allowedValues = [ "supports", "challenges", "inconclusive" ] }+ , nestedScalar "rationale" "Why the observation bears on the claim."+ ]+ , optional =+ [ nestedScalar+ "outcomeName"+ "Name of the job outcome the claim concerns, when it concerns one."+ ]+ }+ }++let measurements =+ FieldRule::{+ , field = "measurements"+ , description = Some+ "Observed values of a job's measurable outcome, in that outcome's unit."+ , cardinality = Cardinality.List+ , elementFields = Some NestedRules::{+ , required =+ [ nestedScalar "name" "Stable measurement name, unique within this finding."+ , jobRef+ , nestedScalar "outcomeName" "Name of the measured outcome on the job."+ , nestedScalar "value" "Observed finite number in `unit`."+ , nestedScalar "unit" "Unit of `value`; the outcome's declared unit."+ , nestedScalar "method" "How the value was obtained."+ , nestedScalar "population" "Whose behavior the value describes."+ , nestedRfc3339 "periodStart" "UTC RFC3339 start of the observed period."+ , nestedRfc3339 "periodEnd" "UTC RFC3339 end of the observed period."+ ]+ }+ , uniqueBy = Some "name"+ }++let finding =+ TypeRule::{+ , type = "Product Research Finding"+ , description = Some+ "One citable observation from a study and the interpretation drawn from it."+ , frontmatter = FrontmatterRules::{+ , required =+ [ productResearchId+ , scalar+ "studyRef"+ "Local PR-N handle or canonical Mori URI of the study producing this finding."+ // { reference = localOrExternalPR }+ , scalar "observation" "What was seen or measured, without interpretation."+ , scalar "interpretation" "The conclusion the authors draw from the observation."+ , scalar "context" "Circumstances in which the observation was made."+ , rfc3339 "observedAt" "UTC RFC3339 time the observation was made."+ , scalar "limitations" "What this finding cannot support."+ , v02.sources+ // { description = Some+ "§5.1. Raw material the observation rests on, possibly outside the bundle."+ }+ , scalar+ "status"+ "Finding lifecycle: `current`, `superseded`, or `withdrawn`."+ // { allowedValues = [ "current", "superseded", "withdrawn" ] }+ , scalar+ "supersededBy"+ "Local PR-N handle or canonical Mori URI of the replacing finding."+ // { reference = localOrExternalPR, when = whenStatus [ "superseded" ] }+ , scalar "withdrawalReason" "Why the finding no longer stands."+ // { when = whenStatus [ "withdrawn" ] }+ ]+ , optional = [ jobAssessments, measurements ]+ }+ , pathPattern = Some "*"+ , idPrefix = Some "PR"+ }++in Profile::{+ , name = "product-research"+ , description = Some+ "Customer and product discovery research with stable PR handles: bounded studies, and separately citable findings that keep observation apart from interpretation and assess named claims about canonical jobs. Distinct from technical research documents."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "productResearchId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar+ "type"+ "The Product Research Study or Product Research Finding concept type."+ , scalar "title" "Human-readable title."+ , scalar "description" "One sentence stating the study or finding."+ , v02.generated+ // { description = Some+ "§5.2. Who produced this document's current content, and when."+ }+ ]+ , optional =+ [ FieldRule::{+ , field = "tags"+ , description = Some "Producer-defined search and grouping tags."+ , cardinality = Cardinality.List+ }+ , reviewRule+ , v02.verified+ // { description = Some+ "§5.2. Independent confirmations that this document is accurate. Mirror an approving `reviews` entry here."+ }+ ]+ }+ , -- Study and finding `status` are house vocabularies (ADR-1); this profile+ -- does not splice OKF v0.2 §5.4's `status` or `stale_after`.+ types = [ study, finding ]+ }
+ test/fixtures/catalogue/profiles/documentation/runbooks.dhall view
@@ -0,0 +1,121 @@+--| Operational procedures with explicit ownership, applicability and effects.+let Profile = ../../Profile/Type.dhall++let FrontmatterRules = ../../Profile/FrontmatterRules.dhall++let TypeRule = ../../Profile/TypeRule.dhall++let okf = ../../Profile/okf.dhall++let FieldRule = okf.defaults.FieldRule++let HandleReferenceRule = okf.defaults.HandleReferenceRule++let Cardinality = okf.Cardinality++let FieldFormat = okf.FieldFormat++let v02 = ../../Profile/V02.dhall++let scalar =+ \(field : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field+ , description = Some description+ , cardinality = Cardinality.Scalar+ }++let list =+ \(field : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field+ , description = Some description+ , cardinality = Cardinality.List+ }++let reference =+ \(field : Text) ->+ \(description : Text) ->+ FieldRule::{+ , field+ , description = Some description+ , reference = Some HandleReferenceRule::{+ , localPrefix = "RB"+ , externalUriSchemes = [ "mori" ]+ }+ }++in Profile::{+ , name = "runbooks"+ , description = Some+ "Operational procedures with stable RB handles, accountable ownership, applicability, triggers and declared effects."+ , okfVersion = "0.2"+ , requireBundleVersion = Some "0.2"+ , allowUnknownTypes = False+ , idField = Some "docId"+ , frontmatter = FrontmatterRules::{+ , required =+ [ scalar "type" "The operational document type."+ , scalar "title" "Human-readable procedure title."+ , scalar "description" "Purpose and scope of the procedure."+ , scalar "docId" "Bundle-scoped stable RB-N handle."+ // { format = Some (FieldFormat.DocumentHandle "RB") }+ , scalar+ "owner"+ "Accountable person, team or role that maintains the procedure and receives escalations."+ , list+ "projects"+ "Canonical Mori URIs for the projects or artifacts affected by this procedure."+ // { format = Some (FieldFormat.UriWithScheme "mori") }+ , list+ "environments"+ "Explicit applicable environments; vocabulary is owned by the repository."+ , scalar+ "trigger"+ "Event or symptom that makes this procedure applicable."+ , list+ "effects"+ "Possible effects of following the procedure, including conditional branches; this grants no authority."+ // { allowedValues =+ [ "read-only"+ , "repository-change"+ , "registry-change"+ , "service-change"+ , "data-change"+ ]+ }+ , list "tags" "Search and discovery terms."+ , v02.status+ // { description = Some+ "Documentation lifecycle: draft, stable or deprecated; this does not certify an operational exercise."+ }+ , v02.generated+ ]+ , recommended = [] : List FieldRule.Type+ , optional =+ [ v02.sources+ , v02.verified+ , v02.staleAfter+ , list+ "related"+ "Supporting alerts, dashboards, failure modes or other operational evidence."+ // { format = Some (FieldFormat.UriWithScheme "mori") }+ , reference+ "supersedes"+ "Earlier runbooks replaced by this procedure."+ // { cardinality = Cardinality.List }+ , reference "supersededBy" "Later runbook replacing this procedure."+ // { cardinality = Cardinality.Scalar }+ ]+ }+ , types =+ [ TypeRule::{+ , type = "Runbook"+ , idPrefix = Some "RB"+ , description = Some+ "An operational procedure whose ordering, prerequisites, verification, stop conditions, recovery and escalation matter."+ }+ ]+ }
− test/fixtures/profile-discovery/dist-newstyle/valid-build-output.dhall
@@ -1,2 +0,0 @@--- Build-output directories are excluded even when they contain valid profiles.-../../profiles/decisions.dhall
+ test/fixtures/profile-nested-closure-invalid/index.md view
@@ -0,0 +1,6 @@+---+okf_version: "0.2"+---+# Nested closure++- [Note](/note.md)
+ test/fixtures/profile-nested-closure-invalid/log.md view
@@ -0,0 +1,4 @@+# Bundle Update Log++## 2026-10-28+* **Add**: Created nested closure fixture.
+ test/fixtures/profile-nested-closure-invalid/note.md view
@@ -0,0 +1,10 @@+---+type: Note+title: Nested closure+description: A nested closure validation fixture.+generated:+ by: human:fixture-author+rows: [{name: a, stray: x}]+settings: {name: settings, extra: null}+---+# Nested closure
+ test/fixtures/profile-nested-closure-valid/index.md view
@@ -0,0 +1,6 @@+---+okf_version: "0.2"+---+# Nested closure++- [Note](/note.md)
+ test/fixtures/profile-nested-closure-valid/log.md view
@@ -0,0 +1,4 @@+# Bundle Update Log++## 2026-10-28+* **Add**: Created nested closure fixture.
+ test/fixtures/profile-nested-closure-valid/note.md view
@@ -0,0 +1,10 @@+---+type: Note+title: Nested closure+description: A nested closure validation fixture.+generated:+ by: human:fixture-author+rows: [{name: a, note: scoped}]+settings: {name: settings}+---+# Nested closure
+ test/fixtures/profile-product-discovery-invalid/index.md view
@@ -0,0 +1,6 @@+---+okf_version: "0.2"+---+# Product discovery++- [Job](/job.md)
+ test/fixtures/profile-product-discovery-invalid/job.md view
@@ -0,0 +1,25 @@+---+type: Job+title: Author a concept+description: A local product discovery fixture.+generated:+ by: human:fixture-author+status: planned+outcomes:+ - name: Faster+ metric: duration+ unit: seconds+ direction: decrease+ method: observation+ baseline: 0+ population: authors+ period: weekly+ periodStart: 2026-10-01T00:00:00.1Z+ periodEnd: 2026-10-01T00:00:00.2Z+jobs: [{name: 42}]+features: [{name: Editor, featureRef: 'https://example.com/editor', jobs: [Author]}]+featureRefs: ['https://example.com/review']+jobRefs: [JOB-1]+outcomeRefs: [{jobRef: JOB-1, outcomeNames: [Faster]}]+---+# Author a concept
+ test/fixtures/profile-product-discovery-invalid/log.md view
@@ -0,0 +1,4 @@+# Bundle Update Log++## 2026-10-27+* **Add**: Created product discovery fixture.
+ test/fixtures/profile-product-discovery-valid/index.md view
@@ -0,0 +1,6 @@+---+okf_version: "0.2"+---+# Product discovery++- [Job](/job.md)
+ test/fixtures/profile-product-discovery-valid/job.md view
@@ -0,0 +1,26 @@+---+type: Job+title: Author a concept+description: A local product discovery fixture.+generated:+ by: human:fixture-author+status: planned+sources: [interview]+outcomes:+ - name: Faster+ metric: duration+ unit: seconds+ direction: decrease+ method: observation+ baseline: 0+ population: authors+ period: weekly+ periodStart: 2026-10-01T00:00:00.1Z+ periodEnd: 2026-10-01T00:00:00.2Z+jobs: [{name: Author}]+features: [{name: Editor, featureRef: 'https://example.com/editor', jobs: [Author]}]+featureRefs: ['https://example.com/review']+jobRefs: [JOB-1]+outcomeRefs: [{jobRef: JOB-1, outcomeNames: [Faster]}]+---+# Author a concept
+ test/fixtures/profile-product-discovery-valid/log.md view
@@ -0,0 +1,4 @@+# Bundle Update Log++## 2026-10-27+* **Add**: Created product discovery fixture.
test/fixtures/profiles/attested-computation-house.dhall view
@@ -1,26 +1,5 @@--- A *house* profile for the OKF v0.2 attested computation contract--- (specification §10), and the descriptor `docs/user/profiles.md` documents.------ Everything here is a house convention rather than a v0.2 rule. §10.2 marks--- exactly one field REQUIRED for this type, `runtime`, and okf's core already--- reports a missing one under `--strict`. Demanding that every parameter carry--- a `type`, or that an executor name a resource the bundle actually holds, is a--- team's own policy — which is why it lives here and not in--- `docs/profiles/okf-v0-2.dhall`, whose job is the format's own rules.------ This file is exercised by `testFrozenFixturesCompile`, so the descriptor--- `docs/user/profiles.md` shows cannot rot into something that no longer--- compiles.------ Two structural points the prose in that document explains, restated where a--- reader of the descriptor will meet them:------ * The whole contract is scoped to one `type` with a `TypeRule`. Profile--- scope would demand `runtime` of every `Metric` in the bundle.--- * `executor` and `attester` are mappings, so their members are reached with--- `objectFields`, while `parameters` is a list of mappings, so its members--- are reached with `elementFields`. Getting that pair the wrong way round is--- the single easiest mistake to make here.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -35,10 +14,6 @@ let nested = ../../../dhall/mk/NestedFieldRule.dhall --- §10.2. A parameter is a named hole an agent may fill. `name` is what the--- computation binds; `type` is what this team insists on so an agent knows what--- kind of value is wanted, and `required` is optional because §10.2 leaves the--- default to the producer. let parameterMembers = NestedRules::{ , required =@@ -51,10 +26,6 @@ , optional = [ nested.boolean "required" ] } --- §10.2. The executor names the run instructions and the receipt fields a run--- must return. `bundlePath` checks that the resource names a file the bundle--- actually holds, which is the whole reason a team writes this rule: a contract--- whose executor cannot be found is a contract that cannot be honoured. let executorMembers = NestedRules::{ , required =@@ -71,8 +42,6 @@ ] } --- §10.2. The attester is deterministic, no-LLM code that inspects a receipt.--- Same path policy, same reason. let attesterMembers = NestedRules::{ , required =@@ -88,8 +57,8 @@ "A house convention for the OKF v0.2 attested computation contract." , guidance = None Text , okfVersion = "0.2"- , frontmatter =- { required = [ field.plain "type" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" ] , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }@@ -102,8 +71,8 @@ , type = "Attested Computation" , description = Some "A sanctioned computation, with the means to check that a value came from running it."- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.recordList "parameters" parameterMembers // { description = Some "The typed named holes an agent may fill. This team requires at least the declaration, so a computation taking none says so with an empty list."
test/fixtures/profiles/cardinality.dhall view
@@ -1,3 +1,5 @@+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/FieldRule.dhall@@ -10,8 +12,8 @@ , description = Some "Exercises scalar and list field cardinality." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.scalar "title" , field.list "tags"@@ -27,8 +29,8 @@ , types = [ TypeRule::{ , type = "Cardinality Concept"- , frontmatter =- { required = [ field.scalar "score" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.scalar "score" ] , recommended = [] : List FieldRule , optional = [] : List FieldRule }
test/fixtures/profiles/closed-fields.dhall view
@@ -1,3 +1,5 @@+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -7,11 +9,12 @@ let field = ../../../dhall/mk/FieldRule.dhall in { name = "closed-fields"- , description = Some "Exercises closed field names and value vocabularies."+ , description = Some+ "Exercises closed field names and value vocabularies." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.enum "status" [ "proposed", "accepted", "closed" ] ]@@ -25,8 +28,8 @@ , types = [ TypeRule::{ , type = "Improvement Request"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.enum "status" [ "proposed", "accepted" ] , field.plain "owner" ]@@ -36,8 +39,8 @@ } , TypeRule::{ , type = "Review"- , frontmatter =- { required = [ field.plain "reviewer" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "reviewer" ] , recommended = [] : List FieldRule , optional = [] : List FieldRule }
test/fixtures/profiles/concept-filters.dhall view
@@ -1,19 +1,5 @@---| House profile for the concept-filter fixture bundle.------ Mirrors the shape of the improvement-request profile this repository's own--- `docs/improvement-requests/` bundle uses, small enough to reason about and--- resolvable entirely from local relative imports so the test suites stay--- offline.------ Three declarations here carry the weight of the `okf concepts --profile`--- tests. `status` is both an OKF v0.2 core key and a profile-declared closed--- vocabulary, which is what proves a profile rule outranks the core key list.--- `targetPlan` is declared only on `Improvement Request`, which is what proves--- that restricting the check to the types `--type` named is a real restriction.--- `noteKind` is declared plainly profile-wide and closed on `Note` alone, which--- is the shape that catches a check treating the profile-wide rules as a scope--- of their own: there `noteKind` has an empty allowed-value list, and an empty--- list means unconstrained.+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/defaults/FieldRule.dhall@@ -40,7 +26,8 @@ , optional = [ FieldRule::{ , field = "status"- , allowedValues = [ "proposed", "accepted", "completed", "rejected" ]+ , allowedValues =+ [ "proposed", "accepted", "completed", "rejected" ] , cardinality = Cardinality.Scalar } , field.documentHandle "requestId" "IR"@@ -49,7 +36,8 @@ , field.rfc3339Utc "completedAt" , field.record "generated"- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "by" , format = Some FieldFormat.Actor@@ -65,7 +53,8 @@ } , field.recordList "reviews"- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "kind" , allowedValues = [ "human", "model" ]
test/fixtures/profiles/conditional-fields-invalid.dhall view
@@ -1,3 +1,5 @@+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -8,8 +10,8 @@ in Profile::{ , name = "invalid-conditional-fields"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "status" , allowedValues = [ "active" ]
test/fixtures/profiles/conditional-fields.dhall view
@@ -1,3 +1,7 @@+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall++let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -10,13 +14,14 @@ let Cardinality = okf.Cardinality -let condition = \(field : Text) -> \(hasValue : List Text) -> { field, hasValue }+let condition =+ \(field : Text) -> \(hasValue : List Text) -> { field, hasValue } let decision = TypeRule::{ , type = "Decision Record"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "status" , allowedValues = [ "active", "superseded" ]@@ -36,8 +41,8 @@ let postgresql = TypeRule::{ , type = "PostgreSQL Derivation"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "derivationKind" , allowedValues = [ "projection", "operational" ]@@ -63,11 +68,12 @@ let reviewed = TypeRule::{ , type = "Reviewed Concept"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ okf.mk.FieldRule.recordList "reviews"- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "kind" , allowedValues = [ "human", "model" ]@@ -106,8 +112,8 @@ in Profile::{ , name = "conditional-fields" , allowUnknownTypes = False- , frontmatter =- { required = [ okf.mk.FieldRule.plain "type" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ okf.mk.FieldRule.plain "type" ] , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }
test/fixtures/profiles/decisions.dhall view
@@ -1,8 +1,5 @@--- The annotation and TypeRule record completion jointly guard the canonical--- schema, its defaults, and the Haskell decoder against drift. This fixture--- writes its frontmatter with the `mk/FieldRule.dhall` constructors; the--- postgresql fixture writes its own with record completion, so both authoring--- forms stay exercised by the suite.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -13,15 +10,16 @@ , description = Some "How this team records architectural decisions." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.documented "type" "The OKF concept type; must be a type rule below." , field.plain "title" ] , recommended =- [ field.documented "status" "One of: proposed, accepted, superseded." ]+ [ field.documented "status" "One of: proposed, accepted, superseded."+ ] , optional = [ field.documented "supersedes"
test/fixtures/profiles/document-references-invalid.dhall view
@@ -1,5 +1,5 @@---| Semantically invalid reference policies; Dhall accepts the shape and--- compileProfile rejects the policy errors before bundle traversal.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -25,8 +25,8 @@ , description = None Text , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ rule "badPrefix" "1ADR" ([] : List Text) , rule "undeclared" "PAT" ([] : List Text) , rule "badScheme" "ADR" [ "mori_" ]@@ -47,8 +47,8 @@ , types = [ TypeRule::{ , type = "Decision Record"- , frontmatter =- { required = [ rule "conflict" "RFC" ([] : List Text) ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ rule "conflict" "RFC" ([] : List Text) ] , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }
test/fixtures/profiles/document-references.dhall view
@@ -1,4 +1,5 @@---| Current schema fixture for bundle-local handles and explicit external URIs.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -13,14 +14,16 @@ let references = (field.localOrExternalReference "references" "ADR" [ "mori" ])- with cardinality = Cardinality.List+ with cardinality = Cardinality.List let selfReference = FieldRule::{ , field = "selfReference" , cardinality = Cardinality.Scalar- , reference =- Some HandleReferenceRule::{ localPrefix = "ADR", allowSelf = True }+ , reference = Some HandleReferenceRule::{+ , localPrefix = "ADR"+ , allowSelf = True+ } } in { name = "document-references"@@ -28,8 +31,8 @@ "Architecture decisions with bundle-local or explicitly external references." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required = [ field.plain "type", field.plain "title" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type", field.plain "title" ] , recommended = [ references, selfReference ] , optional = [] : List FieldRule.Type }
test/fixtures/profiles/formats.dhall view
@@ -1,3 +1,5 @@+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/defaults/FieldRule.dhall@@ -14,8 +16,8 @@ , description = Some "Exercises named textual field formats." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.rfc3339Utc "timestamp" , field.date "published"@@ -37,8 +39,8 @@ , types = [ TypeRule::{ , type = "Format Concept"- , frontmatter =- { required = [ field.uriWithScheme "homepage" "https" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.uriWithScheme "homepage" "https" ] , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }
test/fixtures/profiles/guidance.dhall view
@@ -1,6 +1,5 @@---| Focused acceptance fixture for profile-wide and type-specific authoring--- guidance. This proves descriptor semantics; it is not a published house QA--- profile.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -19,8 +18,8 @@ Keep supporting checks in source control and make the observed result reproducible. '' , okfVersion = "0.2"- , frontmatter =- { required = [ okf.mk.FieldRule.plain "type" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ okf.mk.FieldRule.plain "type" ] , recommended = [] : List FieldRule , optional = [] : List FieldRule }
+ test/fixtures/profiles/nested-closure-malformed-type.dhall view
@@ -0,0 +1,18 @@+-- Only the type scope carries nested closure; the root flag is historical.+let okf = ../../../dhall/package.dhall++let current = ./nested-closure.dhall++let malformed = okf.defaults.NestedRules::{=} // { allowUnknownFields = "closed" }++in current //+ { frontmatter = okf.defaults.FrontmatterRules::{=}+ , types =+ [ okf.defaults.TypeRule::{ type = "Note" } //+ { frontmatter = okf.defaults.FrontmatterRules::{=} //+ { required =+ [ okf.defaults.FieldRule::{ field = "settings" } // { objectFields = Some malformed } ]+ }+ }+ ]+ }
+ test/fixtures/profiles/nested-closure-malformed.dhall view
@@ -0,0 +1,13 @@+-- Width extraction could discard this malformed new member and reopen the record.+let okf = ../../../dhall/package.dhall++let current = ./nested-closure.dhall++let malformed = okf.defaults.NestedRules::{=} // { allowUnknownFields = "closed" }++in current //+ { frontmatter = current.frontmatter //+ { optional =+ [ okf.defaults.FieldRule::{ field = "rows" } // { elementFields = Some malformed } ]+ }+ }
+ test/fixtures/profiles/nested-closure-registry.dhall view
@@ -0,0 +1,1 @@+{ nestedClosure = ./nested-closure.dhall }
+ test/fixtures/profiles/nested-closure.dhall view
@@ -0,0 +1,43 @@+let okf = ../../../dhall/package.dhall++let scalar = \(field : Text) -> okf.defaults.NestedFieldRule::{ field, cardinality = okf.Cardinality.Scalar }++let members = okf.defaults.NestedRules::{+ , allowUnknownFields = False+ , required = [ scalar "name" ]+ , optional =+ [ scalar "start" // { format = Some okf.FieldFormat.Rfc3339Utc }+ , scalar "end" // { format = Some okf.FieldFormat.Rfc3339Utc }+ ]+ , presenceConstraints =+ [ okf.defaults.PresenceConstraint::{+ , whenAnyPresent = [ okf.defaults.ValueSelector::{ field = "start" } ]+ , requireAll = [ okf.defaults.ValueSelector::{ field = "end" } ]+ }+ ]+ , timestampOrderConstraints = [ { before = "start", after = "end" } ]+ }++in okf.defaults.Profile::{+ , name = "nested-closure"+ , okfVersion = "0.2"+ , frontmatter = okf.defaults.FrontmatterRules::{+ , optional =+ [ okf.defaults.FieldRule::{ field = "rows", elementFields = Some members }+ , okf.defaults.FieldRule::{ field = "settings", objectFields = Some members }+ ]+ }+ , types =+ [ okf.defaults.TypeRule::{+ , type = "Note"+ , frontmatter = okf.defaults.FrontmatterRules::{+ , optional =+ [ okf.defaults.FieldRule::{+ , field = "rows"+ , elementFields = Some okf.defaults.NestedRules::{ optional = [ scalar "note" ] }+ }+ ]+ }+ }+ ]+ }
test/fixtures/profiles/nested-references-and-uniqueness.dhall view
@@ -1,3 +1,7 @@+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall++let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/defaults/FieldRule.dhall@@ -15,7 +19,8 @@ let field = ../../../dhall/mk/FieldRule.dhall let dependencyRules =- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "ref" , cardinality = Cardinality.Scalar@@ -33,7 +38,8 @@ } let acceptanceCriteriaRules =- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "id" , cardinality = Cardinality.Scalar@@ -50,8 +56,8 @@ "Exercises external-only nested Mori references and list-local acceptance criterion IDs." , guidance = None Text , okfVersion = "0.2"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.documentHandle "requestId" "IR" , field.recordList "dependencies" dependencyRules@@ -65,6 +71,7 @@ , allowUnknownFields = True , idField = Some "requestId" , requireBundleVersion = None Text- , types = [ TypeRule::{ type = "Improvement Request", idPrefix = Some "IR" } ]+ , types =+ [ TypeRule::{ type = "Improvement Request", idPrefix = Some "IR" } ] } : Profile
test/fixtures/profiles/nested-reviews.dhall view
@@ -1,3 +1,7 @@+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall++let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/defaults/FieldRule.dhall@@ -16,18 +20,22 @@ , description = Some "Validates one level of structured review records." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.recordList "reviews"- { required =+ NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "kind" , allowedValues = [ "human", "model" ] }- , NestedFieldRule::{ field = "reviewer", cardinality = Cardinality.Scalar } , NestedFieldRule::{+ , field = "reviewer"+ , cardinality = Cardinality.Scalar+ }+ , NestedFieldRule::{ , field = "reviewed_at" , format = Some FieldFormat.Rfc3339Utc }@@ -47,12 +55,20 @@ } , NestedFieldRule::{ , field = "outcome"- , allowedValues = [ "approved", "changes-requested", "commented" ]+ , allowedValues =+ [ "approved", "changes-requested", "commented" ] }- , NestedFieldRule::{ field = "context", cardinality = Cardinality.Scalar }+ , NestedFieldRule::{+ , field = "context"+ , cardinality = Cardinality.Scalar+ } ] , recommended =- [ NestedFieldRule::{ field = "notes", cardinality = Cardinality.Scalar } ]+ [ NestedFieldRule::{+ , field = "notes"+ , cardinality = Cardinality.Scalar+ }+ ] , optional = [] : List NestedFieldRule.Type } ]
test/fixtures/profiles/optional-collision-invalid.dhall view
@@ -1,8 +1,5 @@---| One key classified by two presence lists at one scope. The profile cannot--- say both "check for this under --strict" and "never check for this", so--- compileProfile rejects it with ConflictingFieldRequirement rather than picking--- a winner. `reviewedBy` collides at profile scope; `owner` collides at type--- scope.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -15,16 +12,16 @@ in Profile::{ , name = "invalid-optional-collisions"- , frontmatter =- { required = [ field.plain "type" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" ] , recommended = [ field.plain "reviewedBy" ] , optional = [ field.plain "reviewedBy" ] } , types = [ TypeRule::{ , type = "Decision Record"- , frontmatter =- { required = [ field.plain "owner" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "owner" ] , recommended = [] : List FieldRule.Type , optional = [ field.plain "owner" ] }
test/fixtures/profiles/optional-conditional-invalid.dhall view
@@ -1,8 +1,7 @@---| A condition on an optional rule is dead: `when` gates a presence clause, and--- an optional rule has none. Dhall accepts the shape; compileProfile rejects it--- with OptionalFieldWithCondition before any concept is read.------ The nested declaration is rejected for the same reason at `reviews.model`.+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall++let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -17,16 +16,16 @@ in Profile::{ , name = "invalid-optional-conditions"- , frontmatter =- { required = [ okf.mk.FieldRule.plain "type" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ okf.mk.FieldRule.plain "type" ] , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type } , types = [ TypeRule::{ , type = "Decision Record"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "status" , allowedValues = [ "accepted", "superseded" ]@@ -35,8 +34,8 @@ , FieldRule::{ , field = "reviews" , cardinality = Cardinality.List- , elementFields = Some- { required =+ , elementFields = Some NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "kind" , allowedValues = [ "human", "model" ]
test/fixtures/profiles/optional-fields.dhall view
@@ -1,15 +1,7 @@---| Exercises the third presence classification end to end.------ `supersedes` and `originatingPlan` are lifecycle and provenance metadata: a--- decision that replaces nothing has no `supersedes` value to give, and not--- every decision came from a plan. They are declared, fully constrained, and--- never reported when absent. `reviewedBy` is a genuine authoring--- recommendation, so `--strict` must still demand it. `supersededBy` shows the--- IR-5 coexistence: a conditional *requirement* in the same type as the optional--- declarations, required only once `status` says the decision was superseded.------ `allowUnknownFields = False` closes the vocabulary, so this fixture also--- proves an optional key counts as declared.+let NestedDefaults = ../../../dhall/defaults/NestedRules.dhall++let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let okf = ../../../dhall/package.dhall let Profile = okf.defaults.Profile@@ -33,8 +25,8 @@ , type = "Decision Record" , pathPattern = Some "decisions/*" , idPrefix = Some "ADR"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "status" , description = Some "Where the decision is in its lifecycle."@@ -65,10 +57,11 @@ } , FieldRule::{ , field = "reviews"- , description = Some "Structured review records, when any were made."+ , description = Some+ "Structured review records, when any were made." , cardinality = Cardinality.List- , elementFields = Some- { required =+ , elementFields = Some NestedDefaults::{+ , required = [ NestedFieldRule::{ , field = "kind" , allowedValues = [ "human", "model" ]@@ -96,8 +89,8 @@ , allowUnknownTypes = False , allowUnknownFields = False , idField = Some "docId"- , frontmatter =- { required = [ field.plain "type", field.plain "title" ]+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type", field.plain "title" ] , recommended = [] : List FieldRule.Type , optional = [ field.documented
test/fixtures/profiles/postgresql.dhall view
@@ -1,9 +1,5 @@--- The `: Profile` annotation here is load-bearing: it ties this fixture to the--- canonical schema, so the `testLoadProfileFixture` round-trip in test/Main.hs--- fails if okf's published Dhall schema and the Haskell decoder ever drift apart.--- Frontmatter here is written with FieldRule record completion; the decisions--- fixture uses the `mk/FieldRule.dhall` constructors instead, so both authoring--- forms stay exercised.+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let FieldRule = ../../../dhall/defaults/FieldRule.dhall@@ -15,8 +11,8 @@ "Conventions for documenting a PostgreSQL database as an OKF bundle." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ FieldRule::{ , field = "type" , description = Some@@ -54,8 +50,8 @@ [ { type = "PostgreSQL Schema" , description = Some "One namespace grouping tables and views." , guidance = None Text- , frontmatter =- { required = [] : List FieldRule.Type+ , frontmatter = FrontmatterDefaults::{+ , required = [] : List FieldRule.Type , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }@@ -69,8 +65,8 @@ , description = Some "One physical table in a schema, including its column list." , guidance = None Text- , frontmatter =- { required = [] : List FieldRule.Type+ , frontmatter = FrontmatterDefaults::{+ , required = [] : List FieldRule.Type , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }@@ -83,8 +79,8 @@ , { type = "PostgreSQL View" , description = Some "One view, including the columns it projects." , guidance = None Text- , frontmatter =- { required = [] : List FieldRule.Type+ , frontmatter = FrontmatterDefaults::{+ , required = [] : List FieldRule.Type , recommended = [] : List FieldRule.Type , optional = [] : List FieldRule.Type }
+ test/fixtures/profiles/pre-nested-closure.dhall view
@@ -0,0 +1,182 @@+--| FROZEN: complete descriptor at cfecb72e543c28f5a77ff852445fdd0f256b8810.+-- Every reachable record and union is inline; no live schema imports.+let Cardinality = < Any | Scalar | List >++let FieldFormat =+ < Rfc3339Utc+ | Date+ | Uri+ | UriWithScheme : Text+ | DocumentHandle : Text+ | Actor+ | HumanActor+ | Integer+ | NonNegativeInteger+ | FiniteDecimal+ | Boolean+ >++let FieldCondition = { field : Text, hasValue : List Text }++let HandleReferenceRule =+ { localPrefix : Text+ , externalUriSchemes : List Text+ , allowSelf : Bool+ , allowLocal : Bool+ , externalUriPattern : Optional Text+ }++let PathReferenceRule = { externalUriSchemes : List Text, allowSelf : Bool }++let NestedFieldRule =+ { field : Text+ , description : Optional Text+ , allowedValues : List Text+ , cardinality : Cardinality+ , format : Optional FieldFormat+ , path : Optional PathReferenceRule+ , when : Optional FieldCondition+ , reference : Optional HandleReferenceRule+ }++let ValueSelector = { field : Text, member : Optional Text }++let PresenceConstraint =+ { when : Optional FieldCondition+ , whenAnyPresent : List ValueSelector+ , requireAll : List ValueSelector+ , requireAny : List ValueSelector+ }++let TimestampOrderConstraint = { before : Text, after : Text }++let MembershipConstraint = { source : ValueSelector, target : ValueSelector }++let UniqueValuesConstraint = { selectors : List ValueSelector }++let NestedRules =+ { required : List NestedFieldRule+ , recommended : List NestedFieldRule+ , optional : List NestedFieldRule+ , presenceConstraints : List PresenceConstraint+ , timestampOrderConstraints : List TimestampOrderConstraint+ }++let FieldRule =+ { field : Text+ , description : Optional Text+ , allowedValues : List Text+ , cardinality : Cardinality+ , format : Optional FieldFormat+ , elementFields : Optional NestedRules+ , objectFields : Optional NestedRules+ , reference : Optional HandleReferenceRule+ , path : Optional PathReferenceRule+ , when : Optional FieldCondition+ , uniqueBy : Optional Text+ }++let FrontmatterRules =+ { required : List FieldRule+ , recommended : List FieldRule+ , optional : List FieldRule+ , presenceConstraints : List PresenceConstraint+ , timestampOrderConstraints : List TimestampOrderConstraint+ , membershipConstraints : List MembershipConstraint+ , uniqueValuesConstraints : List UniqueValuesConstraint+ }++let plain =+ \(field : Text) ->+ { field+ , description = None Text+ , allowedValues = [] : List Text+ , cardinality = Cardinality.Any+ , format = None FieldFormat+ , elementFields = None NestedRules+ , objectFields = None NestedRules+ , reference = None HandleReferenceRule+ , path = None PathReferenceRule+ , when = None FieldCondition+ , uniqueBy = None Text+ }++let nestedPlain =+ \(field : Text) ->+ { field+ , description = None Text+ , allowedValues = [] : List Text+ , cardinality = Cardinality.Any+ , format = None FieldFormat+ , path = None PathReferenceRule+ , when = None FieldCondition+ , reference = None HandleReferenceRule+ }++let select = \(field : Text) -> { field, member = None Text }++let scalar = \(field : Text) -> nestedPlain field // { cardinality = Cardinality.Scalar }++let members : NestedRules =+ { required = [ scalar "name" ]+ , recommended = [] : List NestedFieldRule+ , optional =+ [ scalar "start" // { format = Some FieldFormat.Rfc3339Utc }+ , scalar "end" // { format = Some FieldFormat.Rfc3339Utc }+ , scalar "score" // { format = Some FieldFormat.FiniteDecimal }+ ]+ , presenceConstraints =+ [ { when = None FieldCondition+ , whenAnyPresent = [ select "start" ]+ , requireAll = [ select "end" ]+ , requireAny = [] : List ValueSelector+ }+ ]+ , timestampOrderConstraints = [ { before = "start", after = "end" } ]+ }++let fields : FrontmatterRules =+ { required = [] : List FieldRule+ , recommended = [] : List FieldRule+ , optional =+ [ plain "rows" // { elementFields = Some members }+ , plain "settings" // { objectFields = Some members }+ , plain "names" // { cardinality = Cardinality.List }+ , plain "start" // { cardinality = Cardinality.Scalar, format = Some FieldFormat.Rfc3339Utc }+ , plain "end" // { cardinality = Cardinality.Scalar, format = Some FieldFormat.Rfc3339Utc }+ ]+ , presenceConstraints =+ [ { when = None FieldCondition+ , whenAnyPresent = [ select "start" ]+ , requireAll = [ select "end" ]+ , requireAny = [] : List ValueSelector+ }+ ]+ , timestampOrderConstraints = [ { before = "start", after = "end" } ]+ , membershipConstraints =+ [ { source = { field = "rows", member = Some "name" }, target = select "names" } ]+ , uniqueValuesConstraints = [ { selectors = [ select "names", { field = "settings", member = Some "name" } ] } ]+ }++in { name = "pre-nested-closure"+ , description = Some "The complete descriptor immediately before nested closure."+ , okfVersion = "0.2"+ , guidance = Some "Frozen guidance"+ , frontmatter = fields+ , allowUnknownTypes = False+ , allowUnknownFields = False+ , idField = None Text+ , requireBundleVersion = Some "0.2"+ , types =+ [ { type = "Note"+ , description = None Text+ , guidance = Some "Frozen type guidance"+ , frontmatter = fields+ , pathPattern = None Text+ , resourceScheme = None Text+ , requireSchemaSection = False+ , schemaColumns = [] : List Text+ , idPrefix = None Text+ }+ ]+ }
+ test/fixtures/profiles/pre-product-discovery-0.10.0.0.dhall view
@@ -0,0 +1,177 @@+--| Frozen public descriptor generation from okf-core 0.10.0.0.+-- Every record and union is inline so this fixture cannot silently acquire new+-- members from the live schema. The final value is deliberately unannotated.+-- FROZEN: do not edit after release.+let Cardinality = < Any | Scalar | List >++let FieldFormat =+ < Rfc3339Utc+ | Date+ | Uri+ | UriWithScheme : Text+ | DocumentHandle : Text+ | Actor+ | HumanActor+ | Integer+ | NonNegativeInteger+ | Boolean+ >++let FieldCondition = { field : Text, hasValue : List Text }++let HandleReferenceRule =+ { localPrefix : Text+ , externalUriSchemes : List Text+ , allowSelf : Bool+ , allowLocal : Bool+ , externalUriPattern : Optional Text+ }++let PathReferenceRule = { externalUriSchemes : List Text, allowSelf : Bool }++let NestedFieldRule =+ { field : Text+ , description : Optional Text+ , allowedValues : List Text+ , cardinality : Cardinality+ , format : Optional FieldFormat+ , path : Optional PathReferenceRule+ , when : Optional FieldCondition+ , reference : Optional HandleReferenceRule+ }++let NestedRules =+ { required : List NestedFieldRule+ , recommended : List NestedFieldRule+ , optional : List NestedFieldRule+ }++let FieldRule =+ { field : Text+ , description : Optional Text+ , allowedValues : List Text+ , cardinality : Cardinality+ , format : Optional FieldFormat+ , elementFields : Optional NestedRules+ , objectFields : Optional NestedRules+ , reference : Optional HandleReferenceRule+ , path : Optional PathReferenceRule+ , when : Optional FieldCondition+ , uniqueBy : Optional Text+ }++let FrontmatterRules =+ { required : List FieldRule+ , recommended : List FieldRule+ , optional : List FieldRule+ }++let plain =+ \(field : Text) ->+ { field+ , description = None Text+ , allowedValues = [] : List Text+ , cardinality = Cardinality.Any+ , format = None FieldFormat+ , elementFields = None NestedRules+ , objectFields = None NestedRules+ , reference = None HandleReferenceRule+ , path = None PathReferenceRule+ , when = None FieldCondition+ , uniqueBy = None Text+ }++let nestedPlain =+ \(field : Text) ->+ { field+ , description = None Text+ , allowedValues = [] : List Text+ , cardinality = Cardinality.Any+ , format = None FieldFormat+ , path = None PathReferenceRule+ , when = None FieldCondition+ , reference = None HandleReferenceRule+ }++in { name = "pre-product-discovery-0.10.0.0"+ , description = Some "The complete public 0.10.0.0 descriptor shape."+ , okfVersion = "0.2"+ , guidance = Some "Frozen guidance"+ , frontmatter =+ { required =+ [ plain "type"+ , plain "dependencies"+ // { cardinality = Cardinality.List+ , elementFields = Some+ { required =+ [ nestedPlain "id" // { cardinality = Cardinality.Scalar } ]+ , recommended = [] : List NestedFieldRule+ , optional =+ [ nestedPlain "ref"+ // { cardinality = Cardinality.Scalar+ , reference = Some+ { localPrefix = "METRIC"+ , externalUriSchemes = [ "mori" ]+ , allowSelf = False+ , allowLocal = False+ , externalUriPattern = Some "mori://shinzui/.+"+ }+ }+ ]+ }+ , uniqueBy = Some "id"+ }+ , plain "generated"+ // { objectFields = Some+ { required =+ [ nestedPlain "by"+ // { cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Actor+ }+ , nestedPlain "at"+ // { cardinality = Cardinality.Scalar+ , format = Some FieldFormat.Rfc3339Utc+ }+ ]+ , recommended = [] : List NestedFieldRule+ , optional = [] : List NestedFieldRule+ }+ }+ ]+ , recommended =+ [ plain "usage_count"+ // { format = Some FieldFormat.NonNegativeInteger }+ ]+ , optional =+ [ plain "resource"+ // { path = Some+ { externalUriSchemes = [ "https" ], allowSelf = False }+ }+ ]+ }+ , allowUnknownTypes = False+ , allowUnknownFields = True+ , idField = Some "docId"+ , requireBundleVersion = Some "0.2"+ , types =+ [ { type = "Metric"+ , description = Some "A measured quantity."+ , guidance = Some "Frozen guidance"+ , frontmatter =+ { required =+ [ plain "owner"+ // { cardinality = Cardinality.Scalar+ , format = Some FieldFormat.HumanActor+ }+ ]+ , recommended = [] : List FieldRule+ , optional = [] : List FieldRule+ }+ , pathPattern = None Text+ , resourceScheme = None Text+ , requireSchemaSection = False+ , schemaColumns = [] : List Text+ , idPrefix = Some "METRIC"+ }+ ]+ }
+ test/fixtures/profiles/product-discovery-malformed.dhall view
@@ -0,0 +1,2 @@+-- Current-generation members must never disappear through record-width fallback.+./product-discovery.dhall with frontmatter.presenceConstraints = "malformed"
+ test/fixtures/profiles/product-discovery-registry.dhall view
@@ -0,0 +1,1 @@+{ productDiscovery = ./product-discovery.dhall }
+ test/fixtures/profiles/product-discovery.dhall view
@@ -0,0 +1,131 @@+let okf = ../../../dhall/package.dhall++let F = okf.defaults.FieldRule++let N = okf.defaults.NestedFieldRule++let R = okf.defaults.NestedRules++let P = okf.defaults.PresenceConstraint++let s = \(field : Text) -> okf.defaults.ValueSelector::{ field }++let scalar =+ \(field : Text) -> N::{ field, cardinality = okf.Cardinality.Scalar }++let outcomes =+ R::{+ , optional =+ [ scalar "name"+ , scalar "metric"+ , scalar "unit"+ , scalar "direction"+ , scalar "method"+ , scalar "population"+ , scalar "period"+ , scalar "baseline" // { format = Some okf.FieldFormat.FiniteDecimal }+ , scalar "target" // { format = Some okf.FieldFormat.FiniteDecimal }+ , scalar "periodStart" // { format = Some okf.FieldFormat.Rfc3339Utc }+ , scalar "periodEnd" // { format = Some okf.FieldFormat.Rfc3339Utc }+ ]+ , presenceConstraints =+ [ P::{+ , whenAnyPresent =+ [ s "metric"+ , s "unit"+ , s "direction"+ , s "method"+ , s "baseline"+ , s "target"+ , s "population"+ , s "period"+ ]+ , requireAll = [ s "metric", s "unit", s "direction", s "method" ]+ }+ , P::{+ , whenAnyPresent = [ s "baseline" ]+ , requireAll = [ s "population", s "period" ]+ }+ ]+ , timestampOrderConstraints =+ [ { before = "periodStart", after = "periodEnd" } ]+ }++let jobs = R::{ required = [ scalar "name" ] }++let features =+ R::{+ , optional =+ [ scalar "name"+ , scalar "featureRef"+ , N::{ field = "jobs", cardinality = okf.Cardinality.List }+ ]+ }++let links =+ R::{+ , optional =+ [ scalar "jobRef"+ , N::{ field = "outcomeNames", cardinality = okf.Cardinality.List }+ ]+ , presenceConstraints =+ [ P::{+ , whenAnyPresent = [ s "outcomeNames" ]+ , requireAll = [ s "jobRef" ]+ }+ ]+ }++in okf.defaults.Profile::{+ , name = "product-discovery"+ , okfVersion = "0.2"+ , frontmatter = okf.defaults.FrontmatterRules::{+ , optional =+ [ F::{ field = "outcomes", elementFields = Some outcomes }+ , F::{ field = "sources", cardinality = okf.Cardinality.List }+ , F::{+ , field = "status"+ , cardinality = okf.Cardinality.Scalar+ , allowedValues = [ "draft", "planned", "in-progress", "delivered" ]+ }+ , F::{+ , field = "jobs"+ , elementFields = Some jobs+ , uniqueBy = Some "name"+ }+ , F::{ field = "jobRefs", cardinality = okf.Cardinality.List }+ , F::{ field = "features", elementFields = Some features }+ , F::{ field = "featureRefs", cardinality = okf.Cardinality.List }+ , F::{ field = "outcomeRefs", elementFields = Some links }+ ]+ , presenceConstraints =+ [ P::{+ , whenAnyPresent =+ [ { field = "outcomes", member = Some "baseline" } ]+ , requireAll = [ s "sources" ]+ }+ , P::{+ , when = Some+ { field = "status"+ , hasValue = [ "planned", "in-progress", "delivered" ]+ }+ , requireAny = [ s "features", s "featureRefs" ]+ }+ ]+ , membershipConstraints =+ [ { source = { field = "outcomeRefs", member = Some "jobRef" }+ , target = s "jobRefs"+ }+ , { source = { field = "features", member = Some "jobs" }+ , target = { field = "jobs", member = Some "name" }+ }+ ]+ , uniqueValuesConstraints =+ [ { selectors =+ [ s "featureRefs"+ , { field = "features", member = Some "featureRef" }+ ]+ }+ ]+ }+ }
test/fixtures/profiles/type-frontmatter.dhall view
@@ -1,3 +1,5 @@+let FrontmatterDefaults = ../../../dhall/defaults/FrontmatterRules.dhall+ let Profile = ../../../dhall/Profile.dhall let TypeRule = ../../../dhall/defaults/TypeRule.dhall@@ -7,11 +9,12 @@ let FieldRule = ../../../dhall/FieldRule.dhall in { name = "type-frontmatter"- , description = Some "Exercises profile-wide and type-specific field rules."+ , description = Some+ "Exercises profile-wide and type-specific field rules." , guidance = None Text , okfVersion = "0.1"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.plain "type" , field.documented "title" "Human-readable concept title." ]@@ -25,11 +28,14 @@ , types = [ TypeRule::{ , type = "Owned Concept"- , frontmatter =- { required =+ , frontmatter = FrontmatterDefaults::{+ , required = [ field.documented "owner" "Person responsible for the concept." ] , recommended =- [ field.documented "reviewer" "Person who independently reviewed it." ]+ [ field.documented+ "reviewer"+ "Person who independently reviewed it."+ ] , optional = [] : List FieldRule } }