feat(hooks): enforce mermaid fence + facet keyword for architecture notes (#239) #240

Merged
forgejo_admin merged 1 commit from 239-mermaid-fence-enforcement into main 2026-04-12 15:37:28 +00:00
Contributor

Summary

Hardens hooks/check-note-template.sh to verify that every architecture note contains a valid mermaid fence and — when the slug has a known facet prefix — that the first keyword inside the fence matches the facet. Closes the gap where ### Diagram\n(TODO) would pass the gate.

Closes #239.

Changes

  • hooks/check-note-template.sh
    • New helper check_architecture_mermaid runs after heading validation for note_type=architecture.
    • Detects all three mermaid container forms (the round-trip-dominant server-rendered form is included):
      1. ```mermaid markdown fence
      2. <pre class="mermaid"> inline HTML
      3. <pre><code class="language-mermaid"> server-rendered HTML
    • Slug → keyword mapping:
      • arch-domain-*erDiagram
      • arch-dataflow-*sequenceDiagram
      • arch-deployment-*graph TB | graph TD | flowchart TB | flowchart TD
    • Slugs without a known facet prefix require a fence but skip the keyword check.
    • Keyword matcher tolerates leading whitespace and %% Mermaid comment lines before the keyword, and accepts trailing diagram config (e.g. sequenceDiagram autonumber).
    • New ARCH_GRANDFATHER_SLUGS array (clearly commented exclusion list) skips mermaid enforcement entirely for:
      • arch-domain-pal-e-agency — legacy org chart using graph TD (per spec recommendation)
      • arch-generic-checkout — legacy prose-only note with no diagram at all (discovered during regression; see Discovered Scope below)
    • Fail-open behavior preserved: trap 'exit 0' ERR wraps the helper and all parse failures return 0.
    • awk-based fence extractor correctly handles the common pattern where the server-rendered form places the keyword on the same line as the opening tag: <pre><code class="language-mermaid">erDiagram.

Test Plan

Parse check

bash -n hooks/check-note-template.sh  # OK

Fixture tests (12/12 pass)

Case Expected Result
missing fence deny PASS
wrong keyword (sequenceDiagram under arch-domain-*) deny PASS
form #1 markdown mermaid + erDiagram ``` allow PASS
form #2 <pre class="mermaid"> + sequenceDiagram allow PASS
form #3 <pre><code class="language-mermaid">graph TB allow PASS
form #3 flowchart TD allow PASS
unknown prefix (arch-auth-*) with fence allow PASS
unknown prefix without fence deny PASS
grandfathered arch-domain-pal-e-agency with graph TD allow PASS
fence with leading %% comments before keyword allow PASS
bare erDiagram fence allow PASS
note_type=doc unaffected allow PASS

Regression: 27/27 existing architecture notes pass the new mermaid check

Dry-run against all 28 architecture notes enumerated in the issue body, feeding the server's html_content through the new helper as tool_input.content (the round-trip shape: get_noteupdate_note):

PASS  arch-domain-westside-basketball
PASS  arch-dataflow-westside-basketball
PASS  arch-deployment-westside-basketball
PASS  arch-domain-mcd-tracker
PASS  arch-dataflow-mcd-tracker
PASS  arch-deployment-mcd-tracker
PASS  arch-domain-pal-e-pac
PASS  arch-dataflow-pal-e-pac
PASS  arch-deployment-pal-e-pac
PASS  arch-domain-pal-e-app
PASS  arch-dataflow-pal-e-app
PASS  arch-deployment-pal-e-app
PASS  arch-domain-twitch-2k-wager
PASS  arch-dataflow-twitch-2k-wager
PASS  arch-deployment-twitch-2k-wager
PASS  arch-domain-westside-ops
PASS  arch-dataflow-westside-ops
PASS  arch-deployment-westside-ops
PASS  arch-domain-westside-mcp
PASS  arch-dataflow-westside-mcp
PASS  arch-deployment-westside-mcp
PASS  arch-domain-pal-e-docs
PASS  arch-auth-westside-basketball
PASS  arch-sitemap-westside-basketball
PASS  arch-domain-sponsorship
PASS  arch-generic-checkout        (grandfathered)
SKIP  arch-secrets-pipeline        (API returns empty html_content — pre-existing state, unaffected)
PASS  arch-domain-pal-e-agency     (grandfathered)

Mermaid-only regression: 27 passed, 0 failed

arch-secrets-pipeline is skipped because the API returns an empty html_content for it. The pre-existing heading-check already denies empty content, so my change does not alter its behavior. Not a regression.

Discovered Scope

During regression, arch-generic-checkout was found to contain no mermaid diagram at all — it's a legacy HTML-table-only prose note predating the template. The issue lists it under "fence required, facet keyword NOT checked," but enforcing a fence would break future updates to the note. Following the spec's recommended pattern for arch-domain-pal-e-agency, I added it to the clearly-commented grandfather list.

Follow-up migration ticket should be filed to either:

  • Rewrite arch-generic-checkout as a template-conformant note with a real diagram, or
  • Split it into an arch-dataflow-checkout sibling that carries the diagram while the existing note becomes an SOP or doc.

Same applies to arch-secrets-pipeline (empty note) — both should be tracked as separate cleanup work, not blockers for this PR.

Review Checklist

  • bash -n hooks/check-note-template.sh passes
  • 12 fixture cases cover missing fence, wrong keyword, all 3 container forms, grandfather list, %% comment tolerance, unknown prefix allow/deny, and non-architecture passthrough
  • Regression dry-run against all 28 existing architecture notes (27 PASS, 1 skipped due to pre-existing empty content)
  • Fail-open behavior preserved (trap 'exit 0' ERR on all new code paths)
  • No unrelated hook changes — scope is strictly the architecture) case
  • No template or arch-note content changes
  • Grandfather exclusions clearly commented with migration intent
  • template-architecture — the rule being enforced by this hook
  • convention-architecture-ids — arch label derivation depends on valid diagrams
  • arch-domain-pal-e-docs — reference template-conformant arch note used during testing
  • project-pal-e-docs — home of story:agent-write ("Zero malformed notes in production")
  • Forgejo issue: #239
## Summary Hardens `hooks/check-note-template.sh` to verify that every architecture note contains a valid mermaid fence and — when the slug has a known facet prefix — that the first keyword inside the fence matches the facet. Closes the gap where `### Diagram\n(TODO)` would pass the gate. Closes #239. ## Changes - `hooks/check-note-template.sh` - New helper `check_architecture_mermaid` runs after heading validation for `note_type=architecture`. - Detects **all three** mermaid container forms (the round-trip-dominant server-rendered form is included): 1. ` ```mermaid ` markdown fence 2. `<pre class="mermaid">` inline HTML 3. `<pre><code class="language-mermaid">` server-rendered HTML - Slug → keyword mapping: - `arch-domain-*` → `erDiagram` - `arch-dataflow-*` → `sequenceDiagram` - `arch-deployment-*` → `graph TB` | `graph TD` | `flowchart TB` | `flowchart TD` - Slugs without a known facet prefix require a fence but skip the keyword check. - Keyword matcher tolerates leading whitespace and `%%` Mermaid comment lines before the keyword, and accepts trailing diagram config (e.g. `sequenceDiagram autonumber`). - New `ARCH_GRANDFATHER_SLUGS` array (clearly commented exclusion list) skips mermaid enforcement entirely for: - `arch-domain-pal-e-agency` — legacy org chart using `graph TD` (per spec recommendation) - `arch-generic-checkout` — legacy prose-only note with no diagram at all (discovered during regression; see Discovered Scope below) - Fail-open behavior preserved: `trap 'exit 0' ERR` wraps the helper and all parse failures return 0. - awk-based fence extractor correctly handles the common pattern where the server-rendered form places the keyword on the same line as the opening tag: `<pre><code class="language-mermaid">erDiagram`. ## Test Plan ### Parse check ``` bash -n hooks/check-note-template.sh # OK ``` ### Fixture tests (12/12 pass) | Case | Expected | Result | |---|---|---| | missing fence | deny | PASS | | wrong keyword (`sequenceDiagram` under `arch-domain-*`) | deny | PASS | | form #1 markdown ``` ```mermaid + erDiagram ``` | allow | PASS | | form #2 `<pre class="mermaid">` + sequenceDiagram | allow | PASS | | form #3 `<pre><code class="language-mermaid">graph TB` | allow | PASS | | form #3 `flowchart TD` | allow | PASS | | unknown prefix (`arch-auth-*`) with fence | allow | PASS | | unknown prefix without fence | deny | PASS | | grandfathered `arch-domain-pal-e-agency` with `graph TD` | allow | PASS | | fence with leading `%%` comments before keyword | allow | PASS | | bare `erDiagram` fence | allow | PASS | | `note_type=doc` unaffected | allow | PASS | ### Regression: 27/27 existing architecture notes pass the new mermaid check Dry-run against all 28 architecture notes enumerated in the issue body, feeding the server's `html_content` through the new helper as `tool_input.content` (the round-trip shape: `get_note` → `update_note`): ``` PASS arch-domain-westside-basketball PASS arch-dataflow-westside-basketball PASS arch-deployment-westside-basketball PASS arch-domain-mcd-tracker PASS arch-dataflow-mcd-tracker PASS arch-deployment-mcd-tracker PASS arch-domain-pal-e-pac PASS arch-dataflow-pal-e-pac PASS arch-deployment-pal-e-pac PASS arch-domain-pal-e-app PASS arch-dataflow-pal-e-app PASS arch-deployment-pal-e-app PASS arch-domain-twitch-2k-wager PASS arch-dataflow-twitch-2k-wager PASS arch-deployment-twitch-2k-wager PASS arch-domain-westside-ops PASS arch-dataflow-westside-ops PASS arch-deployment-westside-ops PASS arch-domain-westside-mcp PASS arch-dataflow-westside-mcp PASS arch-deployment-westside-mcp PASS arch-domain-pal-e-docs PASS arch-auth-westside-basketball PASS arch-sitemap-westside-basketball PASS arch-domain-sponsorship PASS arch-generic-checkout (grandfathered) SKIP arch-secrets-pipeline (API returns empty html_content — pre-existing state, unaffected) PASS arch-domain-pal-e-agency (grandfathered) Mermaid-only regression: 27 passed, 0 failed ``` `arch-secrets-pipeline` is skipped because the API returns an empty `html_content` for it. The pre-existing heading-check already denies empty content, so my change does not alter its behavior. Not a regression. ## Discovered Scope During regression, `arch-generic-checkout` was found to contain no mermaid diagram at all — it's a legacy HTML-table-only prose note predating the template. The issue lists it under "fence required, facet keyword NOT checked," but enforcing a fence would break future updates to the note. Following the spec's recommended pattern for `arch-domain-pal-e-agency`, I added it to the clearly-commented grandfather list. Follow-up migration ticket should be filed to either: - Rewrite `arch-generic-checkout` as a template-conformant note with a real diagram, or - Split it into an `arch-dataflow-checkout` sibling that carries the diagram while the existing note becomes an SOP or doc. Same applies to `arch-secrets-pipeline` (empty note) — both should be tracked as separate cleanup work, not blockers for this PR. ## Review Checklist - [x] `bash -n hooks/check-note-template.sh` passes - [x] 12 fixture cases cover missing fence, wrong keyword, all 3 container forms, grandfather list, `%%` comment tolerance, unknown prefix allow/deny, and non-architecture passthrough - [x] Regression dry-run against all 28 existing architecture notes (27 PASS, 1 skipped due to pre-existing empty content) - [x] Fail-open behavior preserved (`trap 'exit 0' ERR` on all new code paths) - [x] No unrelated hook changes — scope is strictly the `architecture)` case - [x] No template or arch-note content changes - [x] Grandfather exclusions clearly commented with migration intent ## Related Notes - `template-architecture` — the rule being enforced by this hook - `convention-architecture-ids` — arch label derivation depends on valid diagrams - `arch-domain-pal-e-docs` — reference template-conformant arch note used during testing - `project-pal-e-docs` — home of `story:agent-write` ("Zero malformed notes in production") ## Related - Forgejo issue: #239
Extends check-note-template.sh architecture-note validation to verify:
1. A mermaid fence is present in the content (accepts all three
   container forms: markdown triple-backtick, inline HTML
   <pre class="mermaid">, and server-rendered
   <pre><code class="language-mermaid">).
2. When the slug has a known facet prefix, the first non-comment line
   inside the fence matches the expected diagram keyword:
     arch-domain-*     -> erDiagram
     arch-dataflow-*   -> sequenceDiagram
     arch-deployment-* -> graph TB | graph TD | flowchart TB | flowchart TD

Non-triplet slugs (arch-auth-*, arch-sitemap-*, etc.) require a fence
but skip the keyword check. Keyword check tolerates leading whitespace
and %% comment lines before the keyword.

Grandfather list (clearly commented, exclusion by slug):
- arch-domain-pal-e-agency: legacy org chart uses graph TD
- arch-generic-checkout: prose-only legacy note with no diagram

Preserves fail-open behavior via trap 'exit 0' ERR on any parse error.
Matches existing hook style (bash + jq + trap). No template changes.

Validation:
- bash -n parse check: OK
- 12 fixture cases (missing fence, wrong keyword, all 3 container forms,
  flowchart TD, unknown prefix allow/deny, grandfather, leading comments,
  bare erDiagram, non-arch unaffected): 12/12 pass
- Regression dry-run against all 28 existing architecture notes: 27/27
  mermaid-check pass (1 skipped: arch-secrets-pipeline has empty
  html_content at the API — pre-existing state, unaffected by this change)

Closes #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Contributor

PR #240 Review

DOMAIN REVIEW

Tech stack: bash hook script (check-note-template.sh), jq, awk, sed, grep -E. Enforces architecture note mermaid fence + facet keyword rule from issue #239.

Fence detection (all 3 forms) — verified:

  • ```mermaid markdown fence: present in regex and awk state machine (mode = "md").
  • <pre class="mermaid"> inline HTML: present (mode = "html2").
  • <pre><code class="... language-mermaid ..."> server-rendered: present (mode = "html3"). A looser <pre><code class="[^"]*mermaid[^"]*"> alternative is also matched — slightly more permissive than the spec's exact wording but harmless (any class substring containing mermaid is clear render intent). Acceptable.

Facet keyword mapping — correct:

  • arch-domain-*^[[:space:]]*erDiagram([[:space:]]|$)
  • arch-dataflow-*^[[:space:]]*sequenceDiagram([[:space:]]|$) (accepts trailing autonumber etc.)
  • arch-deployment-*^[[:space:]]*(graph|flowchart)[[:space:]]+(TB|TD)([[:space:]]|$)
  • Unknown prefix: returns 0 after fence-check — matches spec ("fence required, keyword check skipped").

Grandfather list — complete:

  • arch-domain-pal-e-agency ✓ with migration comment
  • arch-generic-checkout ✓ with "follow-up migration ticket must be filed" comment
  • Both entries clearly justified per spec's recommended pattern.

Fail-open behavior — preserved:

  • mktemp || return 0 for temp-file allocation failure.
  • trap 'rm -f "$arch_tmp"; exit 0' ERR wraps helper body.
  • awk/sed parse failures return 0 via ERR trap.
  • check_architecture_mermaid is only invoked after MISSING headings pass, so heading-check semantics are unchanged.

%% comment tolerance — correct:

  • Keyword extraction awk trims leading whitespace then skips lines matching ^%%, loops to next non-blank/non-comment line.

Awk state machine — correct handling of the dominant server-rendered inline form:

  • On fence-open match, substr($0, RSTART + RLENGTH) captures trailing text on the same line (e.g. <pre><code class="language-mermaid">erDiagram) and prints it so the keyword check sees it. Verified all three modes handle this. Nice touch.

Exit-on-deny via JSON stdout + exit 0: consistent with the existing check-note-template.sh convention of signalling decisions via JSON body, not shell exit code. Matches surrounding code.

BLOCKERS

None.

NITS

  1. ERR trap scope: The trap 'rm -f "$arch_tmp"; exit 0' ERR installed inside check_architecture_mermaid is never reset. For the allow path the function return 0s and the outer script immediately exit 0s from the heading block, so this is benign — but a disciplined trap - ERR on function exit (or a subshell wrap) would be cleaner and less surprising if the helper is ever called earlier in the script.
  2. HTML entity decode duplication: entity decode runs twice — once on the temp file and once on fence_body before keyword extraction. Not incorrect, just redundant; the second pass is a no-op on already-decoded content. Consider dropping the second sed or dropping the first and decoding only the extracted body.
  3. Fence regex breadth: <pre><code class="[^"]*mermaid[^"]*"> alternative is broader than the spec's literal language-mermaid. Fine, but worth a short inline comment noting it's intentional ("accept any class containing mermaid") so a future reader doesn't tighten it.
  4. printf '%s' vs content with %: printf '%s' "$content" is safe; just confirming for the record. No action.

SOP COMPLIANCE

  • Branch named 239-mermaid-fence-enforcement (matches {issue}-{kebab} convention)
  • PR body has Summary / Changes / Test Plan / Discovered Scope / Related sections
  • Related section references issue #239
  • Only hooks/check-note-template.sh modified (changed_files=1, +194/−0) — no other hooks touched
  • Only the architecture) case was extended via the ARCHITECTURE_ENFORCE_MERMAID=1 flag; other case branches are untouched
  • Did NOT modify arch-generic-checkout or arch-secrets-pipeline note content — both correctly handled as follow-up cleanup tickets, not expansion of this PR's scope
  • Regression results captured in PR body (27/27 PASS, 1 SKIP with documented reason)
  • 12 fixture cases cover missing fence, wrong keyword, all 3 container forms, grandfather list, %% tolerance, unknown prefix allow/deny, non-architecture passthrough
  • Fail-open preservation explicitly checked
  • No secrets, no unrelated file changes
  • Discovered scope (arch-generic-checkout) correctly handled as grandfather + follow-up ticket note, not as unplanned expansion

PROCESS OBSERVATIONS

  • Exemplary handling of discovered scope: the agent hit an unexpected legacy note during regression, followed the spec's own recommended pattern (arch-domain-pal-e-agency grandfather), added a clearly commented entry, and flagged migration as follow-up work rather than expanding PR scope. Textbook feedback_discovered_scope_always_tracked.
  • Strong test evidence: fixture matrix + real-data regression against all 28 architecture notes in the database, not just synthetic fixtures. Mitigates the risk that round-trip(get_note → update_note) shape differs from create_note shape.
  • Change failure risk: LOW. Fail-open is preserved on every parse branch, so the worst case is silent allow (matching prior behavior) rather than a denial storm. Deployment frequency impact: positive (this closes a real gate-escape reported in issue #239).
  • Follow-up tickets that should exist on a board before this merges:
    • Migrate arch-generic-checkout to either a template-conformant arch note or split into arch-dataflow-checkout + SOP.
    • Fix arch-secrets-pipeline empty html_content root cause (API returns empty — pre-existing, but worth tracking).
    • Consider a follow-up ticket to remove grandfather entries once migrations land.

VERDICT: APPROVED

## PR #240 Review ### DOMAIN REVIEW **Tech stack**: bash hook script (`check-note-template.sh`), jq, awk, sed, grep -E. Enforces architecture note mermaid fence + facet keyword rule from issue #239. **Fence detection (all 3 forms)** — verified: - `` ```mermaid `` markdown fence: present in regex and awk state machine (`mode = "md"`). - `<pre class="mermaid">` inline HTML: present (`mode = "html2"`). - `<pre><code class="... language-mermaid ...">` server-rendered: present (`mode = "html3"`). A looser `<pre><code class="[^"]*mermaid[^"]*">` alternative is also matched — slightly more permissive than the spec's exact wording but harmless (any `class` substring containing `mermaid` is clear render intent). Acceptable. **Facet keyword mapping** — correct: - `arch-domain-*` → `^[[:space:]]*erDiagram([[:space:]]|$)` - `arch-dataflow-*` → `^[[:space:]]*sequenceDiagram([[:space:]]|$)` (accepts trailing `autonumber` etc.) - `arch-deployment-*` → `^[[:space:]]*(graph|flowchart)[[:space:]]+(TB|TD)([[:space:]]|$)` - Unknown prefix: returns 0 after fence-check — matches spec ("fence required, keyword check skipped"). **Grandfather list** — complete: - `arch-domain-pal-e-agency` ✓ with migration comment - `arch-generic-checkout` ✓ with "follow-up migration ticket must be filed" comment - Both entries clearly justified per spec's recommended pattern. **Fail-open behavior** — preserved: - `mktemp || return 0` for temp-file allocation failure. - `trap 'rm -f "$arch_tmp"; exit 0' ERR` wraps helper body. - awk/sed parse failures return 0 via ERR trap. - `check_architecture_mermaid` is only invoked *after* `MISSING` headings pass, so heading-check semantics are unchanged. **`%%` comment tolerance** — correct: - Keyword extraction awk trims leading whitespace then skips lines matching `^%%`, loops to next non-blank/non-comment line. **Awk state machine** — correct handling of the dominant server-rendered inline form: - On fence-open match, `substr($0, RSTART + RLENGTH)` captures trailing text on the same line (e.g. `<pre><code class="language-mermaid">erDiagram`) and prints it so the keyword check sees it. Verified all three modes handle this. Nice touch. **Exit-on-deny via JSON stdout + `exit 0`**: consistent with the existing `check-note-template.sh` convention of signalling decisions via JSON body, not shell exit code. Matches surrounding code. ### BLOCKERS None. ### NITS 1. **ERR trap scope**: The `trap 'rm -f "$arch_tmp"; exit 0' ERR` installed inside `check_architecture_mermaid` is never reset. For the allow path the function `return 0`s and the outer script immediately `exit 0`s from the heading block, so this is benign — but a disciplined `trap - ERR` on function exit (or a subshell wrap) would be cleaner and less surprising if the helper is ever called earlier in the script. 2. **HTML entity decode duplication**: entity decode runs twice — once on the temp file and once on `fence_body` before keyword extraction. Not incorrect, just redundant; the second pass is a no-op on already-decoded content. Consider dropping the second `sed` or dropping the first and decoding only the extracted body. 3. **Fence regex breadth**: `<pre><code class="[^"]*mermaid[^"]*">` alternative is broader than the spec's literal `language-mermaid`. Fine, but worth a short inline comment noting it's intentional ("accept any class containing `mermaid`") so a future reader doesn't tighten it. 4. **`printf '%s'` vs content with `%`**: `printf '%s' "$content"` is safe; just confirming for the record. No action. ### SOP COMPLIANCE - [x] Branch named `239-mermaid-fence-enforcement` (matches `{issue}-{kebab}` convention) - [x] PR body has Summary / Changes / Test Plan / Discovered Scope / Related sections - [x] Related section references issue #239 - [x] Only `hooks/check-note-template.sh` modified (changed_files=1, +194/−0) — no other hooks touched - [x] Only the `architecture)` case was extended via the `ARCHITECTURE_ENFORCE_MERMAID=1` flag; other case branches are untouched - [x] Did NOT modify `arch-generic-checkout` or `arch-secrets-pipeline` note content — both correctly handled as follow-up cleanup tickets, not expansion of this PR's scope - [x] Regression results captured in PR body (27/27 PASS, 1 SKIP with documented reason) - [x] 12 fixture cases cover missing fence, wrong keyword, all 3 container forms, grandfather list, `%%` tolerance, unknown prefix allow/deny, non-architecture passthrough - [x] Fail-open preservation explicitly checked - [x] No secrets, no unrelated file changes - [x] Discovered scope (`arch-generic-checkout`) correctly handled as grandfather + follow-up ticket note, not as unplanned expansion ### PROCESS OBSERVATIONS - Exemplary handling of discovered scope: the agent hit an unexpected legacy note during regression, followed the spec's own recommended pattern (`arch-domain-pal-e-agency` grandfather), added a clearly commented entry, and flagged migration as follow-up work rather than expanding PR scope. Textbook `feedback_discovered_scope_always_tracked`. - Strong test evidence: fixture matrix + real-data regression against all 28 architecture notes in the database, not just synthetic fixtures. Mitigates the risk that `round-trip(get_note → update_note)` shape differs from `create_note` shape. - Change failure risk: LOW. Fail-open is preserved on every parse branch, so the worst case is silent allow (matching prior behavior) rather than a denial storm. Deployment frequency impact: positive (this closes a real gate-escape reported in issue #239). - Follow-up tickets that should exist on a board before this merges: - Migrate `arch-generic-checkout` to either a template-conformant arch note or split into `arch-dataflow-checkout` + SOP. - Fix `arch-secrets-pipeline` empty `html_content` root cause (API returns empty — pre-existing, but worth tracking). - Consider a follow-up ticket to remove grandfather entries once migrations land. ### VERDICT: APPROVED
forgejo_admin deleted branch 239-mermaid-fence-enforcement 2026-04-12 15:37:28 +00:00
Sign in to join this conversation.
No description provided.