diff --git a/.vale.ini b/.vale.ini
new file mode 100644
index 0000000000000000000000000000000000000000..9e0cc21615cbb77aa0eb9801d5a0f51659a517bd
--- /dev/null
+++ b/.vale.ini
@@ -0,0 +1,13 @@
+StylesPath = .vale
+MinAlertLevel = suggestion
+
+Packages = Google
+
+[*.md]
+BasedOnStyles = Vale, Google, Shivering-Isles
+
+Google.Headings = NO
+Google.Quotes = warning
+Google.Spelling = NO
+Google.Units = NO
+Vale.Spelling = NO
diff --git a/.vale/Google/AMPM.yml b/.vale/Google/AMPM.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fbdc6e4f84b95f040ff9b957c459885f16ea9551
--- /dev/null
+++ b/.vale/Google/AMPM.yml
@@ -0,0 +1,9 @@
+extends: existence
+message: "Use 'AM' or 'PM' (preceded by a space)."
+link: 'https://developers.google.com/style/word-list'
+level: error
+nonword: true
+tokens:
+  - '\d{1,2}[AP]M'
+  - '\d{1,2} ?[ap]m'
+  - '\d{1,2} ?[aApP]\.[mM]\.'
diff --git a/.vale/Google/Acronyms.yml b/.vale/Google/Acronyms.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f41af0189b071df79686554e37c9fe2e577d3851
--- /dev/null
+++ b/.vale/Google/Acronyms.yml
@@ -0,0 +1,64 @@
+extends: conditional
+message: "Spell out '%s', if it's unfamiliar to the audience."
+link: 'https://developers.google.com/style/abbreviations'
+level: suggestion
+ignorecase: false
+# Ensures that the existence of 'first' implies the existence of 'second'.
+first: '\b([A-Z]{3,5})\b'
+second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
+# ... with the exception of these:
+exceptions:
+  - API
+  - ASP
+  - CLI
+  - CPU
+  - CSS
+  - CSV
+  - DEBUG
+  - DOM
+  - DPI
+  - FAQ
+  - GCC
+  - GDB
+  - GET
+  - GPU
+  - GTK
+  - GUI
+  - HTML
+  - HTTP
+  - HTTPS
+  - IDE
+  - JAR
+  - JSON
+  - JSX
+  - LESS
+  - LLDB
+  - NET
+  - NOTE
+  - NVDA
+  - OSS
+  - PATH
+  - PDF
+  - PHP
+  - POST
+  - RAM
+  - REPL
+  - RSA
+  - SCM
+  - SCSS
+  - SDK
+  - SQL
+  - SSH
+  - SSL
+  - SVG
+  - TBD
+  - TCP
+  - TODO
+  - URI
+  - URL
+  - USB
+  - UTF
+  - XML
+  - XSS
+  - YAML
+  - ZIP
diff --git a/.vale/Google/Colons.yml b/.vale/Google/Colons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..99363fbd46d78596b22a698524dbb700d94eaf8e
--- /dev/null
+++ b/.vale/Google/Colons.yml
@@ -0,0 +1,8 @@
+extends: existence
+message: "'%s' should be in lowercase."
+link: 'https://developers.google.com/style/colons'
+nonword: true
+level: warning
+scope: sentence
+tokens:
+  - ':\s[A-Z]'
diff --git a/.vale/Google/Contractions.yml b/.vale/Google/Contractions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4f6fd5d489dcb35de7fc7c2ed3c7ad2f5fc731bb
--- /dev/null
+++ b/.vale/Google/Contractions.yml
@@ -0,0 +1,30 @@
+extends: substitution
+message: "Use '%s' instead of '%s'."
+link: 'https://developers.google.com/style/contractions'
+level: suggestion
+ignorecase: true
+action:
+  name: replace
+swap:
+  are not: aren't
+  cannot: can't
+  could not: couldn't
+  did not: didn't
+  do not: don't
+  does not: doesn't
+  has not: hasn't
+  have not: haven't
+  how is: how's
+  is not: isn't
+  it is: it's
+  should not: shouldn't
+  that is: that's
+  they are: they're
+  was not: wasn't
+  we are: we're
+  we have: we've
+  were not: weren't
+  what is: what's
+  when is: when's
+  where is: where's
+  will not: won't
diff --git a/.vale/Google/DateFormat.yml b/.vale/Google/DateFormat.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e9d227fa13d5e1b016ba0716196467461ff015cf
--- /dev/null
+++ b/.vale/Google/DateFormat.yml
@@ -0,0 +1,9 @@
+extends: existence
+message: "Use 'July 31, 2016' format, not '%s'."
+link: 'https://developers.google.com/style/dates-times'
+ignorecase: true
+level: error
+nonword: true
+tokens:
+  - '\d{1,2}(?:\.|/)\d{1,2}(?:\.|/)\d{4}'
+  - '\d{1,2} (?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)|May|Jun(?:e)|Jul(?:y)|Aug(?:ust)|Sep(?:tember)?|Oct(?:ober)|Nov(?:ember)?|Dec(?:ember)?) \d{4}'
diff --git a/.vale/Google/Ellipses.yml b/.vale/Google/Ellipses.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e070517bfe4c879d96dafd29eea29035661acfd
--- /dev/null
+++ b/.vale/Google/Ellipses.yml
@@ -0,0 +1,9 @@
+extends: existence
+message: "In general, don't use an ellipsis."
+link: 'https://developers.google.com/style/ellipses'
+nonword: true
+level: warning
+action:
+  name: remove
+tokens:
+  - '\.\.\.'
diff --git a/.vale/Google/EmDash.yml b/.vale/Google/EmDash.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1befe72aa88122153b3d06c8e548dd9e7bdcdbdd
--- /dev/null
+++ b/.vale/Google/EmDash.yml
@@ -0,0 +1,12 @@
+extends: existence
+message: "Don't put a space before or after a dash."
+link: 'https://developers.google.com/style/dashes'
+nonword: true
+level: error
+action:
+  name: edit
+  params:
+    - remove
+    - ' '
+tokens:
+  - '\s[—–]\s'
diff --git a/.vale/Google/EnDash.yml b/.vale/Google/EnDash.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b314dc4e98ab65da142ec07681c36f9c126c9e04
--- /dev/null
+++ b/.vale/Google/EnDash.yml
@@ -0,0 +1,13 @@
+extends: existence
+message: "Use an em dash ('—') instead of '–'."
+link: 'https://developers.google.com/style/dashes'
+nonword: true
+level: error
+action:
+  name: edit
+  params:
+    - replace
+    - '-'
+    - '—'
+tokens:
+  - '–'
diff --git a/.vale/Google/Exclamation.yml b/.vale/Google/Exclamation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eea5fd24bd5ffb8ad33aae354212523ab637edfb
--- /dev/null
+++ b/.vale/Google/Exclamation.yml
@@ -0,0 +1,9 @@
+extends: existence
+message: "Don't use exclamation points in text."
+link: 'https://developers.google.com/style/exclamation-points'
+nonword: true
+level: error
+action:
+  name: remove
+tokens:
+  - '\w+!(?:\s|$)'
diff --git a/.vale/Google/FirstPerson.yml b/.vale/Google/FirstPerson.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0b7b8828ca5f47acd4329a04ea692bef3ed4b4e0
--- /dev/null
+++ b/.vale/Google/FirstPerson.yml
@@ -0,0 +1,13 @@
+extends: existence
+message: "Avoid first-person pronouns such as '%s'."
+link: 'https://developers.google.com/style/pronouns#personal-pronouns'
+ignorecase: true
+level: warning
+nonword: true
+tokens:
+  - (?:^|\s)I\s
+  - (?:^|\s)I,\s
+  - \bI'm\b
+  - \bme\b
+  - \bmy\b
+  - \bmine\b
diff --git a/.vale/Google/Gender.yml b/.vale/Google/Gender.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c8486181d69775397acfb195d14ba13d3452cfd2
--- /dev/null
+++ b/.vale/Google/Gender.yml
@@ -0,0 +1,9 @@
+extends: existence
+message: "Don't use '%s' as a gender-neutral pronoun."
+link: 'https://developers.google.com/style/pronouns#gender-neutral-pronouns'
+level: error
+ignorecase: true
+tokens:
+  - he/she
+  - s/he
+  - \(s\)he
diff --git a/.vale/Google/GenderBias.yml b/.vale/Google/GenderBias.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9e70190863028083468390ad760d0b41ce9319d3
--- /dev/null
+++ b/.vale/Google/GenderBias.yml
@@ -0,0 +1,47 @@
+extends: substitution
+message: "Consider using '%s' instead of '%s'."
+link: 'https://developers.google.com/style/inclusive-documentation'
+ignorecase: true
+level: error
+action:
+  name: replace
+swap:
+  (?:alumna|alumnus):          graduate
+  (?:alumnae|alumni):          graduates
+  air(?:m[ae]n|wom[ae]n):      pilot(s)
+  anchor(?:m[ae]n|wom[ae]n):   anchor(s)
+  authoress:                   author
+  camera(?:m[ae]n|wom[ae]n):   camera operator(s)
+  chair(?:m[ae]n|wom[ae]n):    chair(s)
+  congress(?:m[ae]n|wom[ae]n): member(s) of congress
+  door(?:m[ae]|wom[ae]n):      concierge(s)
+  draft(?:m[ae]n|wom[ae]n):    drafter(s)
+  fire(?:m[ae]n|wom[ae]n):     firefighter(s)
+  fisher(?:m[ae]n|wom[ae]n):   fisher(s)
+  fresh(?:m[ae]n|wom[ae]n):    first-year student(s)
+  garbage(?:m[ae]n|wom[ae]n):  waste collector(s)
+  lady lawyer:                 lawyer
+  ladylike:                    courteous
+  landlord:                    building manager
+  mail(?:m[ae]n|wom[ae]n):     mail carriers
+  man and wife:                husband and wife
+  man enough:                  strong enough
+  mankind:                     human kind
+  manmade:                     manufactured
+  manpower:                    personnel
+  men and girls:               men and women
+  middle(?:m[ae]n|wom[ae]n):   intermediary
+  news(?:m[ae]n|wom[ae]n):     journalist(s)
+  ombuds(?:man|woman):         ombuds
+  oneupmanship:                upstaging
+  poetess:                     poet
+  police(?:m[ae]n|wom[ae]n):   police officer(s)
+  repair(?:m[ae]n|wom[ae]n):   technician(s)
+  sales(?:m[ae]n|wom[ae]n):    salesperson or sales people
+  service(?:m[ae]n|wom[ae]n):  soldier(s)
+  steward(?:ess)?:             flight attendant
+  tribes(?:m[ae]n|wom[ae]n):   tribe member(s)
+  waitress:                    waiter
+  woman doctor:                doctor
+  woman scientist[s]?:         scientist(s)
+  work(?:m[ae]n|wom[ae]n):     worker(s)
diff --git a/.vale/Google/HeadingPunctuation.yml b/.vale/Google/HeadingPunctuation.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b538be5b42a2ece2723ce07fd2cca46133ee6b28
--- /dev/null
+++ b/.vale/Google/HeadingPunctuation.yml
@@ -0,0 +1,13 @@
+extends: existence
+message: "Don't put a period at the end of a heading."
+link: 'https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings'
+nonword: true
+level: warning
+scope: heading
+action:
+  name: edit
+  params:
+    - remove
+    - '.'
+tokens:
+  - '[a-z0-9][.]\s*$'
diff --git a/.vale/Google/Headings.yml b/.vale/Google/Headings.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e34d001b770e839a37bbb1aafe9448616e7aa503
--- /dev/null
+++ b/.vale/Google/Headings.yml
@@ -0,0 +1,28 @@
+extends: capitalization
+message: "'%s' should use sentence-style capitalization."
+link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings"
+level: warning
+scope: heading
+match: $sentence
+indicators:
+  - ":"
+exceptions:
+  - Azure
+  - CLI
+  - Cosmos
+  - Docker
+  - Emmet
+  - gRPC
+  - I
+  - Kubernetes
+  - Linux
+  - macOS
+  - Marketplace
+  - MongoDB
+  - REPL
+  - Studio
+  - TypeScript
+  - URLs
+  - Visual
+  - VS
+  - Windows
diff --git a/.vale/Google/Latin.yml b/.vale/Google/Latin.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ca03b9154b16308b55da938c06c0f49bdc89b534
--- /dev/null
+++ b/.vale/Google/Latin.yml
@@ -0,0 +1,11 @@
+extends: substitution
+message: "Use '%s' instead of '%s'."
+link: 'https://developers.google.com/style/abbreviations'
+ignorecase: true
+level: error
+nonword: true
+action:
+  name: replace
+swap:
+  '\b(?:eg|e\.g\.)(?=[\s,;])': for example
+  '\b(?:ie|i\.e\.)(?=[\s,;])': that is
diff --git a/.vale/Google/LyHyphens.yml b/.vale/Google/LyHyphens.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac8f557a4af750b57399b936c72339f430d8d7bd
--- /dev/null
+++ b/.vale/Google/LyHyphens.yml
@@ -0,0 +1,14 @@
+extends: existence
+message: "'%s' doesn't need a hyphen."
+link: 'https://developers.google.com/style/hyphens'
+level: error
+ignorecase: false
+nonword: true
+action:
+  name: edit
+  params:
+    - replace
+    - '-'
+    - ' '
+tokens:
+  - '\s[^\s-]+ly-'
diff --git a/.vale/Google/OptionalPlurals.yml b/.vale/Google/OptionalPlurals.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f858ea6fee16aa19fd153538180e4c0657064b7a
--- /dev/null
+++ b/.vale/Google/OptionalPlurals.yml
@@ -0,0 +1,12 @@
+extends: existence
+message: "Don't use plurals in parentheses such as in '%s'."
+link: 'https://developers.google.com/style/plurals-parentheses'
+level: error
+nonword: true
+action:
+  name: edit
+  params:
+    - remove
+    - '(s)'
+tokens:
+  - '\b\w+\(s\)'
diff --git a/.vale/Google/Ordinal.yml b/.vale/Google/Ordinal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d1ac7d27e80d91f4954a95f2b32df47cd35e58a7
--- /dev/null
+++ b/.vale/Google/Ordinal.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Spell out all ordinal numbers ('%s') in text."
+link: 'https://developers.google.com/style/numbers'
+level: error
+nonword: true
+tokens:
+  - \d+(?:st|nd|rd|th)
diff --git a/.vale/Google/OxfordComma.yml b/.vale/Google/OxfordComma.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b9ba21ebb25abdda3495a6560f8c07101c0b43ab
--- /dev/null
+++ b/.vale/Google/OxfordComma.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Use the Oxford comma in '%s'."
+link: 'https://developers.google.com/style/commas'
+scope: sentence
+level: warning
+tokens:
+  - '(?:[^,]+,){1,}\s\w+\s(?:and|or)'
diff --git a/.vale/Google/Parens.yml b/.vale/Google/Parens.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3b8711d0c88f90ff94e75200022bb0f8755f2755
--- /dev/null
+++ b/.vale/Google/Parens.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Use parentheses judiciously."
+link: 'https://developers.google.com/style/parentheses'
+nonword: true
+level: suggestion
+tokens:
+  - '\(.+\)'
diff --git a/.vale/Google/Passive.yml b/.vale/Google/Passive.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3265890e5202bebe66cccf9551dad02b8702f9c6
--- /dev/null
+++ b/.vale/Google/Passive.yml
@@ -0,0 +1,184 @@
+extends: existence
+link: 'https://developers.google.com/style/voice'
+message: "In general, use active voice instead of passive voice ('%s')."
+ignorecase: true
+level: suggestion
+raw:
+  - \b(am|are|were|being|is|been|was|be)\b\s*
+tokens:
+  - '[\w]+ed'
+  - awoken
+  - beat
+  - become
+  - been
+  - begun
+  - bent
+  - beset
+  - bet
+  - bid
+  - bidden
+  - bitten
+  - bled
+  - blown
+  - born
+  - bought
+  - bound
+  - bred
+  - broadcast
+  - broken
+  - brought
+  - built
+  - burnt
+  - burst
+  - cast
+  - caught
+  - chosen
+  - clung
+  - come
+  - cost
+  - crept
+  - cut
+  - dealt
+  - dived
+  - done
+  - drawn
+  - dreamt
+  - driven
+  - drunk
+  - dug
+  - eaten
+  - fallen
+  - fed
+  - felt
+  - fit
+  - fled
+  - flown
+  - flung
+  - forbidden
+  - foregone
+  - forgiven
+  - forgotten
+  - forsaken
+  - fought
+  - found
+  - frozen
+  - given
+  - gone
+  - gotten
+  - ground
+  - grown
+  - heard
+  - held
+  - hidden
+  - hit
+  - hung
+  - hurt
+  - kept
+  - knelt
+  - knit
+  - known
+  - laid
+  - lain
+  - leapt
+  - learnt
+  - led
+  - left
+  - lent
+  - let
+  - lighted
+  - lost
+  - made
+  - meant
+  - met
+  - misspelt
+  - mistaken
+  - mown
+  - overcome
+  - overdone
+  - overtaken
+  - overthrown
+  - paid
+  - pled
+  - proven
+  - put
+  - quit
+  - read
+  - rid
+  - ridden
+  - risen
+  - run
+  - rung
+  - said
+  - sat
+  - sawn
+  - seen
+  - sent
+  - set
+  - sewn
+  - shaken
+  - shaven
+  - shed
+  - shod
+  - shone
+  - shorn
+  - shot
+  - shown
+  - shrunk
+  - shut
+  - slain
+  - slept
+  - slid
+  - slit
+  - slung
+  - smitten
+  - sold
+  - sought
+  - sown
+  - sped
+  - spent
+  - spilt
+  - spit
+  - split
+  - spoken
+  - spread
+  - sprung
+  - spun
+  - stolen
+  - stood
+  - stridden
+  - striven
+  - struck
+  - strung
+  - stuck
+  - stung
+  - stunk
+  - sung
+  - sunk
+  - swept
+  - swollen
+  - sworn
+  - swum
+  - swung
+  - taken
+  - taught
+  - thought
+  - thrived
+  - thrown
+  - thrust
+  - told
+  - torn
+  - trodden
+  - understood
+  - upheld
+  - upset
+  - wed
+  - wept
+  - withheld
+  - withstood
+  - woken
+  - won
+  - worn
+  - wound
+  - woven
+  - written
+  - wrung
diff --git a/.vale/Google/Periods.yml b/.vale/Google/Periods.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d24a6a6c0335d0f20ba1094dcebcf58f290fd2b7
--- /dev/null
+++ b/.vale/Google/Periods.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Don't use periods with acronyms or initialisms such as '%s'."
+link: 'https://developers.google.com/style/abbreviations'
+level: error
+nonword: true
+tokens:
+  - '\b(?:[A-Z]\.){3,}'
diff --git a/.vale/Google/Quotes.yml b/.vale/Google/Quotes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3cb6f1abd182888e5cc78994678ce1ed53dbf921
--- /dev/null
+++ b/.vale/Google/Quotes.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Commas and periods go inside quotation marks."
+link: 'https://developers.google.com/style/quotation-marks'
+level: error
+nonword: true
+tokens:
+  - '"[^"]+"[.,?]'
diff --git a/.vale/Google/Ranges.yml b/.vale/Google/Ranges.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3ec045e777d9ee36f23ec5c98a74aa016bef2ad1
--- /dev/null
+++ b/.vale/Google/Ranges.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Don't add words such as 'from' or 'between' to describe a range of numbers."
+link: 'https://developers.google.com/style/hyphens'
+nonword: true
+level: warning
+tokens:
+  - '(?:from|between)\s\d+\s?-\s?\d+'
diff --git a/.vale/Google/Semicolons.yml b/.vale/Google/Semicolons.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bb8b85b420ee5794a207a350199fee6db636a80c
--- /dev/null
+++ b/.vale/Google/Semicolons.yml
@@ -0,0 +1,8 @@
+extends: existence
+message: "Use semicolons judiciously."
+link: 'https://developers.google.com/style/semicolons'
+nonword: true
+scope: sentence
+level: suggestion
+tokens:
+  - ';'
diff --git a/.vale/Google/Slang.yml b/.vale/Google/Slang.yml
new file mode 100644
index 0000000000000000000000000000000000000000..63f4c248a8413bc0eca4a84de8b55e8fe20397ba
--- /dev/null
+++ b/.vale/Google/Slang.yml
@@ -0,0 +1,11 @@
+extends: existence
+message: "Don't use internet slang abbreviations such as '%s'."
+link: 'https://developers.google.com/style/abbreviations'
+ignorecase: true
+level: error
+tokens:
+  - 'tl;dr'
+  - ymmv
+  - rtfm
+  - imo
+  - fwiw
diff --git a/.vale/Google/Spacing.yml b/.vale/Google/Spacing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..66e45a6b72a95966c45a2ef6d7dda8fad19c93b5
--- /dev/null
+++ b/.vale/Google/Spacing.yml
@@ -0,0 +1,10 @@
+extends: existence
+message: "'%s' should have one space."
+link: 'https://developers.google.com/style/sentence-spacing'
+level: error
+nonword: true
+action:
+  name: remove
+tokens:
+  - '[a-z][.?!] {2,}[A-Z]'
+  - '[a-z][.?!][A-Z]'
diff --git a/.vale/Google/Spelling.yml b/.vale/Google/Spelling.yml
new file mode 100644
index 0000000000000000000000000000000000000000..527ac07d318cb2827a2a4c36629d5037a8ed7dfd
--- /dev/null
+++ b/.vale/Google/Spelling.yml
@@ -0,0 +1,10 @@
+extends: existence
+message: "In general, use American spelling instead of '%s'."
+link: 'https://developers.google.com/style/spelling'
+ignorecase: true
+level: warning
+tokens:
+  - '(?:\w+)nised?'
+  - 'colour'
+  - 'labour'
+  - 'centre'
diff --git a/.vale/Google/Units.yml b/.vale/Google/Units.yml
new file mode 100644
index 0000000000000000000000000000000000000000..53522ab2dea3ef6dd0b034e2c9c8280e02e6e616
--- /dev/null
+++ b/.vale/Google/Units.yml
@@ -0,0 +1,8 @@
+extends: existence
+message: "Put a nonbreaking space between the number and the unit in '%s'."
+link: "https://developers.google.com/style/units-of-measure"
+nonword: true
+level: error
+tokens:
+  - \b\d+(?:B|kB|MB|GB|TB)
+  - \b\d+(?:ns|ms|s|min|h|d)
diff --git a/.vale/Google/We.yml b/.vale/Google/We.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c7ac7d36221d59a7c3101d9c7941d991186218b5
--- /dev/null
+++ b/.vale/Google/We.yml
@@ -0,0 +1,11 @@
+extends: existence
+message: "Try to avoid using first-person plural like '%s'."
+link: 'https://developers.google.com/style/pronouns#personal-pronouns'
+level: warning
+ignorecase: true
+tokens:
+  - we
+  - we'(?:ve|re)
+  - ours?
+  - us
+  - let's
diff --git a/.vale/Google/Will.yml b/.vale/Google/Will.yml
new file mode 100644
index 0000000000000000000000000000000000000000..128a918362b8485dbe8874e613686877bc55dd2f
--- /dev/null
+++ b/.vale/Google/Will.yml
@@ -0,0 +1,7 @@
+extends: existence
+message: "Avoid using '%s'."
+link: 'https://developers.google.com/style/tense'
+ignorecase: true
+level: warning
+tokens:
+  - will
diff --git a/.vale/Google/WordList.yml b/.vale/Google/WordList.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0d675f2372a28021750569ee674addd69c01dff3
--- /dev/null
+++ b/.vale/Google/WordList.yml
@@ -0,0 +1,81 @@
+extends: substitution
+message: "Use '%s' instead of '%s'."
+link: "https://developers.google.com/style/word-list"
+level: warning
+ignorecase: false
+action:
+  name: replace
+swap:
+  "(?:API Console|dev|developer) key": API key
+  "(?:cell ?phone|smart ?phone)": phone|mobile phone
+  "(?:dev|developer|APIs) console": API console
+  "(?:e-mail|Email|E-mail)": email
+  "(?:file ?path|path ?name)": path
+  "(?:kill|terminate|abort)": stop|exit|cancel|end
+  "(?:OAuth ?2|Oauth)": OAuth 2.0
+  "(?:ok|Okay)": OK|okay
+  "(?:WiFi|wifi)": Wi-Fi
+  '[\.]+apk': APK
+  '3\-D': 3D
+  'Google (?:I\-O|IO)': Google I/O
+  "tap (?:&|and) hold": touch & hold
+  "un(?:check|select)": clear
+  above: preceding
+  account name: username
+  action bar: app bar
+  admin: administrator
+  Ajax: AJAX
+  a\.k\.a|aka: or|also known as
+  Android device: Android-powered device
+  android: Android
+  API explorer: APIs Explorer
+  application: app
+  approx\.: approximately
+  authN: authentication
+  authZ: authorization
+  autoupdate: automatically update
+  cellular data: mobile data
+  cellular network: mobile network
+  chapter: documents|pages|sections
+  check box: checkbox
+  check: select
+  CLI: command-line tool
+  click on: click|click in
+  Cloud: Google Cloud Platform|GCP
+  Container Engine: Kubernetes Engine
+  content type: media type
+  curated roles: predefined roles
+  data are: data is
+  Developers Console: Google API Console|API Console
+  disabled?: turn off|off
+  ephemeral IP address: ephemeral external IP address
+  fewer data: less data
+  file name: filename
+  firewalls: firewall rules
+  functionality: capability|feature
+  Google account: Google Account
+  Google accounts: Google Accounts
+  Googling: search with Google
+  grayed-out: unavailable
+  HTTPs: HTTPS
+  in order to: to
+  ingest: import|load
+  k8s: Kubernetes
+  long press: touch & hold
+  network IP address: internal IP address
+  omnibox: address bar
+  open-source: open source
+  overview screen: recents screen
+  regex: regular expression
+  SHA1: SHA-1|HAS-SHA1
+  sign into: sign in to
+  sign-?on: single sign-on
+  static IP address: static external IP address
+  stylesheet: style sheet
+  synch: sync
+  tablename: table name
+  tablet: device
+  touch: tap
+  url: URL
+  vs\.: versus
+  World Wide Web: web
diff --git a/.vale/Google/meta.json b/.vale/Google/meta.json
new file mode 100644
index 0000000000000000000000000000000000000000..a5da2a8480ef2bb82de4c820a6ce505408d4eb92
--- /dev/null
+++ b/.vale/Google/meta.json
@@ -0,0 +1,4 @@
+{
+  "feed": "https://github.com/errata-ai/Google/releases.atom",
+  "vale_version": ">=1.0.0"
+}
diff --git a/.vale/Google/vocab.txt b/.vale/Google/vocab.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.vale/Shivering-Isles/Spelling.yml b/.vale/Shivering-Isles/Spelling.yml
new file mode 100644
index 0000000000000000000000000000000000000000..66b1bfa2b44d4da9b2dd2d3f12c00e5d3ebbc723
--- /dev/null
+++ b/.vale/Shivering-Isles/Spelling.yml
@@ -0,0 +1,5 @@
+extends: spelling
+message: "'%s' is a typo!"
+dicpath: /usr/share/hunspell/
+dictionaries:
+  - en_GB
\ No newline at end of file
diff --git a/Earthfile b/Earthfile
index 7c5e0b1df2d96d62065b7238f822655d6f7b492c..db375d61b7aca0e267b05bc829cc26445daef246 100644
--- a/Earthfile
+++ b/Earthfile
@@ -44,6 +44,24 @@ enable-autoupdates:
     ARG --required node
     RUN kubectl label node "${node}" upgrade.shivering-isles.com/fedora-autoupdate=true
 
+# vale-lint runs vale linting on documentation
+vale-lint:
+    FROM ./images/tools+vale
+    RUN apk add --no-cache hunspell-en
+    WORKDIR /src
+    COPY .vale.ini .vale /src/
+    COPY . /src
+    ARG alertlevel=suggestion
+    RUN vale --minAlertLevel=$alertlevel ./docs
+
+# vale-sync runs a new vale sync locally
+vale-sync:
+    FROM ./images/tools+vale
+    WORKDIR /src
+    COPY .vale.ini /src
+    RUN vale sync
+    SAVE ARTIFACT ./.vale AS LOCAL .vale
+
 # docs runs a local mdbook instance using koolbox that will listen on localhost:3000
 docs:
     WAIT
@@ -51,3 +69,12 @@ docs:
     END
     LOCALLY
     RUN koolbox mdbook serve docs
+
+docs-lint:
+    BUILD +vale-lint --alertlevel=error
+
+docs-build:
+    FROM ./images/tools+mdbook
+    COPY . .
+    RUN mdbook build docs
+    SAVE ARTIFACT ./docs/book AS LOCAL ./docs/book
diff --git a/docs/.gitlab-ci.yaml b/docs/.gitlab-ci.yaml
index cd9c4187a71e76cb8e73ef9f8e6061c018585c93..bf4d768447b49da03327f2528fb7a0240994c3b4 100644
--- a/docs/.gitlab-ci.yaml
+++ b/docs/.gitlab-ci.yaml
@@ -1,6 +1,11 @@
 stages:
     - build
 
+lint-docs:
+  extends: .earthly
+  script:
+    - earthly --ci +docs-lint
+
 build-docs:
   stage: build
   image: docker.io/library/rust
diff --git a/docs/src/apps/keycloak.md b/docs/src/apps/keycloak.md
new file mode 100644
index 0000000000000000000000000000000000000000..6b2fb6ac055631f47bda15a2db9ddd2a1f1f8870
--- /dev/null
+++ b/docs/src/apps/keycloak.md
@@ -0,0 +1,7 @@
+# Keycloak
+
+In the Shivering-Isles Infrastructure Keycloak is the central identity provider. It allows users to manage their sessions and provides Multi-Factor authentication for all services.
+
+The Keycloak instance is usually referred to as "SI-Auth". The Shivering-Isles realm contains the user-base. The Keycloak system realm, called "Master,"  administrates the Shivering-Isles realm.
+
+While the Shivering-Isles realm is accessible over the internet, allowing easy access and authentication from everywhere in the world, the "master" realm is only accessible through the local-network administration endpoint. This reduces the risk of a take over, even if an attacker compromises credentials.
diff --git a/docs/src/components/flux.md b/docs/src/components/flux.md
index 63c524b71bd289630fc8cc0f81b12a44d14eb2fe..d4df9c4f99d760bdda952ae80cfa99afe1d49b8f 100644
--- a/docs/src/components/flux.md
+++ b/docs/src/components/flux.md
@@ -2,7 +2,7 @@
 
 FluxCD is a GitOps controller. It synchronizes the content of a Git repository with a Kubernetes Cluster and makes sure the configurations are applied.
 
-The main advantage over a push based approach such as a CI pipeline, is that a GitOps operator continously reconciles the state and and runs on fully standardised operations. This avoids temporary and custom state in that is common in CI pipelines that might become hard to reproduce once the pipeline is gone.
+The main advantage over a push based approach such as a CI pipeline, is that a GitOps operator continously reconciles the state and runs on fully standardised operations. This avoids temporary and custom state in that is common in CI pipelines that might become hard to reproduce once the pipeline is gone.
 
 Links
 ---
diff --git a/docs/src/concepts/README.md b/docs/src/concepts/README.md
index 74d42e13cdfa301e509e7d989dfc0fcd708fa5b7..8cef1b7a76ddf48775e39e22a9b06c618a539054 100644
--- a/docs/src/concepts/README.md
+++ b/docs/src/concepts/README.md
@@ -1 +1,3 @@
 # Concepts
+
+Just a short section to explain some concepts and their adoption in the Shivering-Isles Infrastructure. The goal is write original documentation for the Shivering-Isles infrastructure instead of copying existing content. The Shivering-Isles documentation links to upstream documentation instead.
\ No newline at end of file
diff --git a/docs/src/concepts/gitops.md b/docs/src/concepts/gitops.md
index a3a47f8e0838e414dc0e3505a8bd69313c665ef0..294cb8debcef0be1a1fa5ef2631a9f65ff30945d 100644
--- a/docs/src/concepts/gitops.md
+++ b/docs/src/concepts/gitops.md
@@ -1,5 +1,5 @@
 # GitOps
 
-The Shivering-Isles Infrastructure uses GitOps as central concept to maintain the Kubernetes cluster and deploy changes to production. Centralising around git as Single Source of Truth without dynmaic state provides an easier way to verify changes. It also reduces the amount of trust put into the CI system by enforcing signed commits on the GitOps operator side.
+The Shivering-Isles Infrastructure uses GitOps as central concept to maintain the Kubernetes cluster and deploy changes to production. Centralising around git as Single Source of Truth without dynamic state provides an easier way to verify changes. It also reduces the amount of trust put into the CI system by enforcing signed commits on the GitOps operator side.
 
 The current tool of choice to implement GitOps in the Shivering-Isles Infrastructure is [FluxCD](https://fluxcd.io/) in combination with a monorepo.
\ No newline at end of file
diff --git a/docs/src/concepts/sre.md b/docs/src/concepts/sre.md
index 672b1d7ed7b9266b20763457a92322d8564422e6..7ce6bfe42e8cbea6619f1eed48f5fed4aa19da7a 100644
--- a/docs/src/concepts/sre.md
+++ b/docs/src/concepts/sre.md
@@ -1,14 +1,14 @@
-# Site reliability engineering
+# Site Reliability Engineering
 
 > Site reliability engineering (SRE) is a set of principles and practices that  applies aspects of software engineering to IT infrastructure and operations.
 >
 > — [Wikipedia](https://en.wikipedia.org/w/index.php?title=Site_reliability_engineering&oldid=1191456683)
 
-In the Shivering-Isles Infrastructure various apps have an own set of SLOs to validate for service degration on changes. It's also a good pratice for SRE in other environments.
+In the Shivering-Isles Infrastructure various apps have an own set of SLOs to validate for service degradation on changes. It's also a good practice for SRE in other environments.
 
-Besides maintaining reasonable SLOs, other SRE practices are implemented, such as post mortems and especially the practice of reducing toil. All components of the infrastructure have a maintence budget, if it's depleted, it's time to fix the apps or get rid of it.
+Besides maintaining reasonable SLOs, other SRE practices are implemented, such as post mortems and especially the practice of reducing toil. All components of the infrastructure have a maintenance budget, if it's depleted, it's time to fix the apps or get rid of it.
 
-Learing about SRE
+Learning about SRE
 ---
 
 A good start is this small video Series by Google:
@@ -16,4 +16,4 @@ A good start is this small video Series by Google:
 <iframe width="100%" height="480" src="https://www.youtube-nocookie.com/embed/?listType=playlist&list=PLIivdWyY5sqJrKl7D2u-gmis8h9K66qoj" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
 
 
-Further there is the [Google SRE book](https://sre.google/sre-book/introduction/), which I can highly recommend.
\ No newline at end of file
+Further there is the [Google SRE book](https://sre.google/sre-book/introduction/) as recommended read.
\ No newline at end of file
diff --git a/docs/src/hardware/node.md b/docs/src/hardware/node.md
index 9e435c679f4201f9b0af049c8c754725ba3c0f16..9fd32636872c28804b087c9354d0126345dadad4 100644
--- a/docs/src/hardware/node.md
+++ b/docs/src/hardware/node.md
@@ -16,7 +16,7 @@ In order to setup the device, install the additional RAM in the underside of the
 
 <iframe width="100%" height="480" src="https://www.youtube-nocookie.com/embed/M1EL8yYe_0s?start=292" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
 
-[Hardware Maintaince Guide](https://download.lenovo.com/pccbbs/thinkcentre_pdf/m75q_gen2_hmm.pdf)
+[Hardware Maintaince Guide](https://web.archive.org/web/20230208212643/https://download.lenovo.com/pccbbs/thinkcentre_pdf/m75q_gen2_hmm.pdf)
 
 ## Power usage
 
diff --git a/docs/src/operating_system.md b/docs/src/operating_system.md
index 06611946ee19e6dcf797e9613134819c2de1ca83..d3645de5aa3cdf47e44abc606c7ea647ad7c879c 100644
--- a/docs/src/operating_system.md
+++ b/docs/src/operating_system.md
@@ -1,6 +1,6 @@
 # Operating System
 
-For this setup Fedora is the Operating System of choice for multiple reasons. It provides both image-based installation methods (e.g. Fedora CoreOS) as well as package-based installation methods (e.g. Fedora Server) for many architectures and provides a modern and stable set of packages. Further it matches the developer machine OS, which helps with debugging and testing things locally before pushing them onto the deployments.
+For this setup Fedora is the Operating System of choice for multiple reasons. It provides both image-based installation methods (for example Fedora CoreOS) as well as package-based installation methods (for example Fedora Server) for many architectures and provides a modern and stable set of packages. Further it matches the developer machine OS, which helps with debugging and testing things locally before pushing them onto the deployments.
 
 ## OS requirements
 
diff --git a/images/tools/Earthfile b/images/tools/Earthfile
index 5b2c40b318d1aae41ac08aca4e8ae81b4f307161..96c1a70db7988213003c11e80df5909dc71d4301 100644
--- a/images/tools/Earthfile
+++ b/images/tools/Earthfile
@@ -144,4 +144,7 @@ mdbook:
 
     RUN curl -L "https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xvzf - mdbook \
         && install -o root -g root -m 0755 mdbook /usr/local/bin/mdbook
-    SAVE ARTIFACT /usr/local/bin/mdbook ./mdbook
\ No newline at end of file
+    SAVE ARTIFACT /usr/local/bin/mdbook ./mdbook
+
+vale:
+    FROM docker.io/jdkato/vale:v2.30.0
\ No newline at end of file