diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,25 @@
 
 ## [Unreleased]
 
+## [0.4.0.0] - 2026-07-30
+
+### Added
+
+- `okf profile show` prints `frontmatter.optional` at profile scope, under each
+  type rule, and inside `elementFields`, using `(none)` when empty, so all three
+  presence lists always print in the same order. Profile JSON emits the matching
+  `optional` key in both rule records.
+- `okf validate` renders the new `OptionalFieldWithCondition` profile-definition
+  error, and the `ConflictingFieldRequirement` message now names all three
+  presence lists. The profiles help topic gains an OPTIONAL FIELDS section and
+  documents when to prefer `optional` over `recommended`.
+
+### Changed
+
+- Requires `okf-core ^>=0.4.0.0`. That release adds the `optional` field to
+  `FrontmatterRules` and `NestedRules`, so a descriptor annotated against okf's
+  current `Profile.dhall` by relative path must declare the third list.
+
 ## [0.3.0.0] - 2026-07-29
 
 ### Added
diff --git a/help/profiles.md b/help/profiles.md
--- a/help/profiles.md
+++ b/help/profiles.md
@@ -21,7 +21,9 @@
                          exit).
 
   --strict               Also check profile `recommended` fields. Required
-                         profile fields are checked in both modes.
+                         profile fields are checked in both modes. Profile
+                         `optional` fields are never reported when absent, in
+                         either mode.
 
 EXIT CODES
 
@@ -64,7 +66,8 @@
 DESCRIPTIONS
 
   A profile may document itself: one description for the profile as a whole,
-  one per required or recommended frontmatter key, and one per type rule.
+  one per required, recommended, or optional frontmatter key, and one per type
+  rule.
   Descriptions are prose for humans -- okf never checks one against a bundle
   and none can produce a deviation.
 
@@ -83,20 +86,22 @@
 
 TYPE-AWARE FRONTMATTER
 
-  Each type rule may add its own required and recommended frontmatter fields.
-  Profile-wide rules apply to every concept; a matching type rule adds to them.
-  Value constraints merge by key, while presence declarations remain separate;
-  an applicable required clause wins over a strict recommendation. Unknown
-  types still receive profile-wide rules.
+  Each type rule may add its own required, recommended, and optional
+  frontmatter fields. Profile-wide rules apply to every concept; a matching type
+  rule adds to them. Value constraints merge by key, while presence declarations
+  remain separate; an applicable required clause wins over a strict
+  recommendation. Declaring a key optional at one scope does not cancel a
+  presence clause the other scope declared. Unknown types still receive
+  profile-wide rules.
 
-  `okf profile show` prints `frontmatter.required` and
-  `frontmatter.recommended` beneath each type. New descriptors should use
+  `okf profile show` prints `frontmatter.required`, `frontmatter.recommended`,
+  and `frontmatter.optional` beneath each type. New descriptors should use
   `okf.defaults.TypeRule::{ ... }`, whose default supplies empty lists.
 
   Before validating a bundle, okf rejects duplicate type rules, repeated keys
-  in one list, and keys placed in both required and recommended at the same
-  scope. These are hard profile-definition errors regardless of
-  `--profile-enforce`.
+  in one list, and keys placed in more than one of required, recommended, and
+  optional at the same scope. These are hard profile-definition errors
+  regardless of `--profile-enforce`.
 
 VALUE VOCABULARIES AND CLOSED FIELDS
 
@@ -162,9 +167,9 @@
 
 NESTED RECORD FIELDS
 
-  A top-level FieldRule may set elementFields to required and recommended rules
-  for every record in a list. The public schema is intentionally bounded to one
-  level: NestedFieldRule has vocabulary, cardinality, and format constraints but
+  A top-level FieldRule may set elementFields to required, recommended, and
+  optional rules for every record in a list. The public schema is intentionally
+  bounded to one level: NestedFieldRule has vocabulary, cardinality, and format constraints but
   cannot contain another elementFields value.
 
   Use field.recordList with NestedFieldRule constructors or record completion.
@@ -173,8 +178,9 @@
   merge by sibling key just like top-level rules.
 
   Each list element must be a record. Required nested keys are always checked;
-  recommended nested keys only under --strict. Present nested values are checked
-  in both modes, and diagnostics identify the exact index:
+  recommended nested keys only under --strict; optional nested keys never.
+  Present nested values are checked in both modes, and diagnostics identify the
+  exact index:
 
     profile: requests/example: missing profile-required field: reviews[2].outcome
     profile: requests/example: frontmatter element at reviews[1] must be a record, found: "not-a-record"
@@ -206,6 +212,30 @@
   Recommended conditions are evaluated only under --strict.
 
     profile: decisions/old: missing profile-required field: supersededBy (when status is superseded)
+
+  `when` is rejected on an optional rule: a condition gates a presence clause,
+  and an optional rule has none, so the pairing would be silently dead.
+
+OPTIONAL FIELDS
+
+  FrontmatterRules and NestedRules carry a third presence list beside required
+  and recommended. An optional field is known to the profile, validated whenever
+  present, and never reported when absent -- in permissive and strict modes
+  alike. Use it for lifecycle or provenance metadata whose absence is ordinary
+  rather than deficient, such as an architecture decision's supersedes.
+
+    , optional = [ field.documented "supersedes" "The decision this replaces." ]
+
+  Optional keys count as declared under allowUnknownFields = False, appear in
+  `okf profile show`, and carry their prose into value diagnostics. Every
+  vocabulary, cardinality, format, reference, and nested-shape check still runs
+  on a present value.
+
+  A field that becomes required under a condition belongs in required with
+  `when = Some ...`, not in optional; the two coexist in one scope.
+
+    profile: decisions/accepted: missing profile-recommended field: reviewedBy
+    profile: decisions/bad: supersedes references ADR-99, which does not exist in this bundle
 
 DOCUMENT REFERENCES
 
diff --git a/okf-cli.cabal b/okf-cli.cabal
--- a/okf-cli.cabal
+++ b/okf-cli.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.4
 name:               okf-cli
-version:            0.3.0.0
+version:            0.4.0.0
 synopsis:           Command-line interface for Open Knowledge Format bundles
 description:
   okf-cli provides the @okf@ executable for working with Open Knowledge Format
@@ -66,7 +66,7 @@
     , generic-lens          >=2.2      && <2.4
     , githash               ^>=0.1
     , lens                  ^>=5.3
-    , okf-core              ^>=0.3.0.0
+    , okf-core              ^>=0.4.0.0
     , optparse-applicative  >=0.18     && <0.20
     , process               >=1.6      && <1.7
     , text                  ^>=2.1
@@ -82,7 +82,7 @@
     , directory
     , filepath
     , okf-cli
-    , okf-core              ^>=0.3.0.0
+    , okf-core              ^>=0.4.0.0
     , optparse-applicative  >=0.18
     , temporary
     , text                  ^>=2.1
diff --git a/src/Okf/Cli.hs b/src/Okf/Cli.hs
--- a/src/Okf/Cli.hs
+++ b/src/Okf/Cli.hs
@@ -64,9 +64,11 @@
     FieldFormat (..),
     FieldPath (..),
     FieldPathSegment (..),
-    FrontmatterRules (..),
+    -- 'FrontmatterRules' and 'NestedRules' are deliberately absent: importing
+    -- their field selectors would make @optional@ ambiguous against
+    -- @optparse-applicative@'s, so this module reads all three presence lists
+    -- through generic-lens labels instead.
     HandleReferenceRule (..),
-    NestedRules (..),
     ProfileDefinitionError (..),
     ProfileSpec (..),
     ProfileViolation (..),
@@ -670,7 +672,7 @@
     { name,
       description,
       okfVersion,
-      frontmatter = FrontmatterRules {required, recommended},
+      frontmatter,
       allowUnknownTypes,
       allowUnknownFields,
       idField,
@@ -684,10 +686,17 @@
       "allowUnknownFields: " <> renderFlag allowUnknownFields,
       "idField: " <> renderOptional idField
     ]
-      <> renderFieldRules "" "frontmatter.required" required
-      <> renderFieldRules "" "frontmatter.recommended" recommended
+      <> renderPresenceLists "" frontmatter
       <> concatMap renderTypeRule typeRules
     where
+      -- The three presence lists always print together and in the same order, at
+      -- profile scope and under every type rule, so the effective policy for one
+      -- key is readable in one place.
+      renderPresenceLists indent rules =
+        renderFieldRules indent "frontmatter.required" (rules ^. #required)
+          <> renderFieldRules indent "frontmatter.recommended" (rules ^. #recommended)
+          <> renderFieldRules indent "frontmatter.optional" (rules ^. #optional)
+
       -- A field's prose cannot share a comma-joined line with its neighbours, so
       -- a non-empty list becomes a headed block. An empty list keeps the
       -- single-line @(none)@ form the other optional fields use.
@@ -706,10 +715,11 @@
         ]
           <> case rule ^. #elementFields of
             Nothing -> [indent <> "    elementFields: (none)"]
-            Just NestedRules {required = nestedRequired, recommended = nestedRecommended} ->
+            Just nestedRules ->
               [indent <> "    elementFields:"]
-                <> renderNestedFieldRules (indent <> "      ") "required" nestedRequired
-                <> renderNestedFieldRules (indent <> "      ") "recommended" nestedRecommended
+                <> renderNestedFieldRules (indent <> "      ") "required" (nestedRules ^. #required)
+                <> renderNestedFieldRules (indent <> "      ") "recommended" (nestedRules ^. #recommended)
+                <> renderNestedFieldRules (indent <> "      ") "optional" (nestedRules ^. #optional)
 
       renderNestedFieldRules indent label [] = [indent <> label <> ": " <> renderList []]
       renderNestedFieldRules indent label rules =
@@ -727,7 +737,7 @@
         TypeRule
           { type_ = ruleType,
             description = ruleDescription,
-            frontmatter = FrontmatterRules {required = typeRequired, recommended = typeRecommended},
+            frontmatter = typeFrontmatter,
             pathPattern,
             resourceScheme,
             requireSchemaSection,
@@ -738,8 +748,7 @@
             "type: " <> ruleType,
             "  description: " <> renderOptional ruleDescription
           ]
-            <> renderFieldRules "  " "frontmatter.required" typeRequired
-            <> renderFieldRules "  " "frontmatter.recommended" typeRecommended
+            <> renderPresenceLists "  " typeFrontmatter
             <> [ "  pathPattern: " <> renderOptional pathPattern,
                  "  resourceScheme: " <> renderOptional resourceScheme,
                  "  requireSchemaSection: " <> renderFlag requireSchemaSection,
@@ -1286,7 +1295,7 @@
   DuplicateFieldRule scope listName key ->
     renderScope scope <> ": duplicate " <> listName <> " field: " <> key
   ConflictingFieldRequirement scope key ->
-    renderScope scope <> ": field appears in required and recommended: " <> key
+    renderScope scope <> ": field appears in more than one of required, recommended, and optional: " <> key
   UnsatisfiableVocabulary scope key profileValues typeValues ->
     renderScope scope
       <> ": disjoint allowed values for "
@@ -1373,6 +1382,10 @@
       <> renderFieldPath target
       <> " cannot also declare format "
       <> renderFieldFormat fieldFormat
+  OptionalFieldWithCondition scope target ->
+    renderScope scope
+      <> ": optional field cannot carry a when condition: "
+      <> renderFieldPath target
   where
     renderScope Nothing = "profile frontmatter"
     renderScope (Just ctype) = "type " <> ctype <> " frontmatter"
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -269,7 +269,8 @@
       frontmatter =
         FrontmatterRules
           { required = [undocumentedField "type", undocumentedField "title"],
-            recommended = []
+            recommended = [],
+            optional = []
           },
       allowUnknownTypes = False,
       allowUnknownFields = True,
@@ -278,7 +279,7 @@
         [ TypeRule
             { type_ = "PostgreSQL Table",
               description = Nothing,
-              frontmatter = FrontmatterRules {required = [], recommended = []},
+              frontmatter = FrontmatterRules {required = [], recommended = [], optional = []},
               pathPattern = Just "schemas/*/tables/*",
               resourceScheme = Just "postgresql",
               requireSchemaSection = True,
@@ -309,7 +310,19 @@
                   },
                 undocumentedField "title"
               ],
-            recommended = []
+            recommended = [],
+            optional =
+              [ FieldRule
+                  { field = "originatingPlan",
+                    description = Just "The plan that produced this decision, when one did.",
+                    allowedValues = [],
+                    cardinality = Scalar,
+                    format = Nothing,
+                    elementFields = Nothing,
+                    reference = Nothing,
+                    when = Nothing
+                  }
+              ]
           },
       allowUnknownTypes = False,
       allowUnknownFields = True,
@@ -321,7 +334,8 @@
               frontmatter =
                 FrontmatterRules
                   { required = [FieldRule "owner" (Just "Person responsible for the decision.") [] Scalar (Just (DocumentHandle "USR")) Nothing Nothing Nothing],
-                    recommended = [FieldRule "reviewer" Nothing ["Ari", "Bo"] List Nothing Nothing (Just (HandleReferenceRule "ADR" ["mori"] False)) Nothing]
+                    recommended = [FieldRule "reviewer" Nothing ["Ari", "Bo"] List Nothing Nothing (Just (HandleReferenceRule "ADR" ["mori"] False)) Nothing],
+                    optional = [FieldRule "supersedes" Nothing [] Scalar Nothing Nothing (Just (HandleReferenceRule "ADR" [] False)) Nothing]
                   },
               pathPattern = Just "decisions/*",
               resourceScheme = Nothing,
@@ -353,13 +367,15 @@
                   ( Just
                       NestedRules
                         { required = [NestedFieldRule "outcome" Nothing ["approved", "rejected"] Any Nothing (Just (FieldCondition "kind" ["model"]))],
-                          recommended = [NestedFieldRule "notes" Nothing [] Scalar Nothing Nothing]
+                          recommended = [NestedFieldRule "notes" Nothing [] Scalar Nothing Nothing],
+                          optional = [NestedFieldRule "model" Nothing [] Scalar Nothing Nothing]
                         }
                   )
                   Nothing
                   Nothing
               ],
-            recommended = []
+            recommended = [],
+            optional = []
           },
       allowUnknownTypes = True,
       allowUnknownFields = True,
@@ -396,7 +412,14 @@
     "          cardinality: scalar",
     "          format: (none)",
     "          when: (none)",
-    "frontmatter.recommended: (none)"
+    "      optional:",
+    "        - model: (none)",
+    "          allowedValues: (any)",
+    "          cardinality: scalar",
+    "          format: (none)",
+    "          when: (none)",
+    "frontmatter.recommended: (none)",
+    "frontmatter.optional: (none)"
   ]
 
 -- | Every optional field prints, as @(none)@ when absent, so the shape does not
@@ -427,6 +450,14 @@
     "    when: (none)",
     "    elementFields: (none)",
     "frontmatter.recommended: (none)",
+    "frontmatter.optional:",
+    "  - originatingPlan: The plan that produced this decision, when one did.",
+    "    allowedValues: (any)",
+    "    cardinality: scalar",
+    "    format: (none)",
+    "    reference: (none)",
+    "    when: (none)",
+    "    elementFields: (none)",
     "",
     "type: Decision Record",
     "  description: One accepted decision, never edited after acceptance.",
@@ -446,6 +477,14 @@
     "      reference: local-prefix(ADR), external-uri-schemes([mori]), allow-self(false)",
     "      when: (none)",
     "      elementFields: (none)",
+    "  frontmatter.optional:",
+    "    - supersedes: (none)",
+    "      allowedValues: (any)",
+    "      cardinality: scalar",
+    "      format: (none)",
+    "      reference: local-prefix(ADR), external-uri-schemes([]), allow-self(false)",
+    "      when: (none)",
+    "      elementFields: (none)",
     "  pathPattern: decisions/*",
     "  resourceScheme: (none)",
     "  requireSchemaSection: false",
@@ -481,11 +520,13 @@
     "    when: (none)",
     "    elementFields: (none)",
     "frontmatter.recommended: (none)",
+    "frontmatter.optional: (none)",
     "",
     "type: PostgreSQL Table",
     "  description: (none)",
     "  frontmatter.required: (none)",
     "  frontmatter.recommended: (none)",
+    "  frontmatter.optional: (none)",
     "  pathPattern: schemas/*/tables/*",
     "  resourceScheme: postgresql",
     "  requireSchemaSection: true",
