Reveal URLs — Arkkitehtuuri
Tämä asiakirja kuvaa, kuinka Reveal URLs -laajennus on rakennettu ja kuinka sen
ydinlogiikka toimii, osallistujille ja teknisille lukijoille. Se viittaa
todellisiin tiedostoihin ja symboleihin kauttaaltaan (muodossa path symbol),
jotta mikä tahansa väite voidaan tarkistaa lähdekoodia vasten. Käyttäjälle
suunnatun kuvauksen löydät käyttöoppaasta; manuaaliset
varmennusvaiheet löydät
manuaalisesta testaussuunnitelmasta.
Yleiskatsaus
Reveal URLs on yksittäinen MV3-WebExtension, joka on rakennettu useille
moottoreille — Chrome, Edge, Opera, Firefox ja Thunderbird — yhdestä jaetusta,
puhtaasta koodiytimestä. (Safari on lykätty: sillä on manifesti ja se on
koottavissa, kun se nimetään erikseen, mutta se on jätetty pois oletuskoonnista
ja CI:stä.) Laajennus paljastaa kunkin linkin kohde-URL-osoitteen linkin vieressä
hahmonnetussa sähköpostissa ja merkitsee linkin, jonka näkyvä teksti nimeää eri
rekisteröitävän verkkotunnuksen kuin sen href — klassinen tietojenkalastelun
merkki.
Tietovarasto on pnpm/TypeScript-monorepo (pnpm-workspace.yaml,
package.json jossa "private": true). Niputus on yksittäinen esbuild-pohjainen
skripti, tooling/build.mjs, joka kääntää jaetut paketit ja kunkin moottorin
ohuet sisääntulopisteet ladattavaan dist/<target>/-hakemistoon.
webextension-polyfill niputetaan jokaiseen skriptiin sen sijaan, että siihen
luotettaisiin ajonaikaisena globaalina, joten sama lähdekoodi toimii
muuttumattomana Chromiumissa ja Geckossa.
Kaksi natiivia sähköpostilisäosaa laajentaa samaa tunnistusydintä pinnoille,
joihin WebExtension ei yllä: Outlook-lisäosa (Office.js-tehtäväruutu, joka
tavoittaa Outlookin verkossa/Windowsissa/Macissa/iOS:ssä/Androidissa ja
Outlook.comin) ja Gmail-lisäosa (Apps Script CardService, joka tavoittaa
Gmailin verkko-/Android-/iOS-sovellukset). Molemmat käyttävät uudelleen vain
PUHDASTA analyysiä — uusi packages/core/src/findings.ts-moduuli
(analyseAnchors/analyseHtml, alustaneutraali Finding[]-malli), joka käyttää
uudelleen olemassa olevaa hostMismatch-logiikkaa ja tldts-rekisteröitävän
verkkotunnuksen johtamista. Vain isäntäsovitin (kuinka runko luetaan ja
jäsennetään) ja esitystapa eroavat pinnoittain: Outlook-tehtäväruutu jäsentää
rungon omalla DOMParser-jäsentimellään ja toimii asiakaspuolella;
Gmail-lisäosa jäsentää node-html-parser-kirjastolla ja toimii
palvelinpuolella Googlen Apps Script V8 -infrastruktuurissa (ilmainen
isännöinti, koska Googlella on jo posti hallussaan; otetaan käyttöön
paikallis-interaktiivisesti clasp-työkalulla). Koska kumpikaan kehys ei voi
muuttaa hahmonnettua, lukutilan viesti-DOMia (Officen
setAsync/prependAsync ovat vain laatimista varten; CardService hahmontaa
kortteja, ei viestin HTML:ää), molemmat lisäosat esittävät havaintojen
paneelin/kortin, eivät sisällytettyä merkintää — olemassa olevaa
linkProcessor.ts-DOM-muutospolkua ja REVEAL_URLS_CSS:ää EI käytetä uudelleen
kummassakaan lisäosassa. Suhteellinen href ratkaistaan vain luotettavaa
sähköpostin sisäistä <base href>-elementtiä vasten ja muutoin ohitetaan, ei
koskaan postilaatikon/palveluntarjoajan alkuperää vasten (mikä keksisi kohteen).
Koodikanta erottaa vastuualueet jyrkästi:
packages/coresisältää PUHTAAN logiikan — ei selain-APIa, ei tallennusta, ei merkkausnieluja. Se on yksikkötestattavissa puhtaalla JavaScriptilla ja on yksi totuuden lähde linkkien merkinnälle, määritysten validoinnille, täsmäyskaavojen täsmäykselle, isäntävertailulle ja kontrastitietoiselle värjäykselle.packages/webextkietoo WebExtension-APIt kyseisen ytimen ympärille: sisältöelinkaaren, dynaamisen sisältöskriptien rekisteröinnin, asetusten käyttöliittymän, taustajohdotuksen, työkalupalkin toiminnon ja Thunderbirdin viestinäyttöpolun.extensions/<engine>sisältää ohuet moottorikohtaiset sisääntulopisteet sekä kunkin moottorinmanifest.json-tiedoston.toolingsisältää koonti-, versioleimaus- ja kuvakkeiden generointiskriptit.featuressisältää Cucumber BDD -skenaariot ja niiden testikaksoset.
Tietovaraston rakenne
packages/core — puhdas ydin
Jokainen tämän moduuli ei tuo mitään laajennus-APIa
(browser.*/chrome.*/messenger.*) eikä käytä mitään
merkkijonosta-merkkaukseen DOM-nielua (innerHTML/insertAdjacentHTML). Tämän
sopimuksen valvoo mekaanisesti packages/core/test/purity.test.ts, joka käy
läpi jokaisen src/**/*.ts-tiedoston, riisuu kommenttirungot omalla
stripComments-toiminnollaan (jotta dokumentaatiolohkot, jotka laillisesti
NIMEÄVÄT kielletyt tokenit, eivät laukaise vartijaa) ja toteaa sitten, ettei
EXTENSION_API_PATTERN eikä UNSAFE_DOM_PATTERN täsmää mihinkään moduuliin.
Julkinen pinta viedään uudelleen packages/core/src/index.ts-tiedostosta. Ainoa
ajonaikainen riippuvuus on tldts (julkisen päätteen tunteva
verkkotunnusvertailu).
Moduulit ovat:
packages/core/src/config.ts—Config/SiteRule-skeema, oletukset ja yksittäinen validointisuppilonormaliseConfig.packages/core/src/matchPattern.ts— täsmäyskaavan kielioppi, täsmääjä, tarkkuusjärjestys ja myönnetyn alkuperän kattavuus.packages/core/src/linkProcessor.ts— asiakirjarajattu merkitsijä.packages/core/src/hostMismatch.ts— rekisteröitävän verkkotunnuksen täsmäämättömyystarkistus.packages/core/src/styles.tsjapackages/core/src/contrast.ts— injektoitava tyylitiedosto ja kontrastitietoinen värjäys.packages/core/src/safeColour.ts— turvallisen värin predikaattiisSafeColour.
packages/webext — selain-API-kääreet
Tämän moduulit VOIVAT käyttää browser.*-rajapintaa ja ovat ainoa paikka, jossa
WebExtension-APeihin kosketaan. Julkinen pinta viedään uudelleen
packages/webext/src/index.ts-tiedostosta. Moduulit ovat: content.ts
(sisältöelinkaari), contentRegistration.ts (dynaaminen rekisteröinti),
storage.ts (määritysten pysyväistallennus), background.ts
(asennuksenaikainen käyttäytyminen), toolbarAction.ts (kytkin + merkki),
messageDisplay.ts ja messageDisplayBackground.ts (Thunderbird) sekä
options/options.ts (asetussivun ohjain).
extensions/<engine> — ohuet sisääntulopisteet ja manifestit
Kukin moottori kantaa oman manifest.json-tiedostonsa ja icons/-kansionsa.
Chrome, Firefox ja Thunderbird toimittavat lisäksi sisääntulopisteiden
src/-kansion; Edge, Opera ja Safari kantavat vain manifestin ja kuvakkeet ja
KÄYTTÄVÄT UUDELLEEN Chromen src/-kansiota (julistettu koontikuvaajassa — katso
alla). Sisääntulopisteet ovat tarkoituksella pieniä: esimerkiksi
extensions/chrome/src/content.ts on vain
void createContentController().bootstrap();, ja
extensions/chrome/src/background.ts kutsuu registerBackground,
registerContentReconciliation ja registerToolbarToggle. Firefoxin
taustasisääntulopiste on identtinen Chromen kanssa; Thunderbirdin
(extensions/thunderbird/src/background.ts) kutsuu sen sijaan registerBackground
ja registerMessageDisplay (sillä ei ole sisältöskriptejä). extensions/_template
on kopioimisvalmis Chromium-teline, ei koontikohde.
tooling, features ja savutestit
tooling sisältää build.mjs, version.mjs ja icons.mjs. features sisältää
Gherkin-skenaariot, niiden askelmäärittelyt ja uskolliset testikaksoset
tiedostoissa features/support/webext.ts ja features/support/world.ts. Kukin
moottori kantaa myös extensions/<engine>/test/manifest.smoke.test.mjs
(_template:n on template.smoke.test.mjs) — nämä ovat sitova manifestisopimus
(katso "Moottorikohtainen koonti ja manifestisopimus" alla).
Paljastusputki
Sisältöpolku alkaa moottorin sisääntulopisteestä kuten
extensions/chrome/src/content.ts, joka kutsuu
createContentController().bootstrap() tiedostosta packages/webext/src/content.ts.
Käynnistys
createContentController bootstrap suoritetaan kerran kehystä kohden:
- Se varaa kehyksen synkronisesti, ennen mitään
await-kutsua,claimBootstrap-toiminnolla, joka lukee/asettaaBOOTSTRAP_MARKER-lipun kehyksenwindow-objektiin. Staattinen sisäänrakennettucontent_scripts-merkintä ja päällekkäinen dynaaminen käyttäjäskripti voivat molemmat injektoidacontent.js:n samaan kehykseen; jaettu eristetyn maailmanwindowtekee tästä oikean kerran-vain-vartijan, ja varaaminen ennen mitäänawait-kutsua tarkoittaa, että kaksi lähes samanaikaista injektiota eivät voi molemmat läpäistä sitä. - Se lataa pysyväistallennetun määrityksen ENSIN
loadConfigWithRetry-toiminnolla (BOOTSTRAP_CONFIG_RETRIESylimääräistä yritystä ohimenevän hylkäyksen yhteydessä), pitäen merkkiä yritysten ajan, jotta päällekkäinen injektio, joka on jo no-op'annut, ei jää jumiin. Vasta kun luku onnistuu, se injektoi tyylitiedoston (injectStyles, joka asettaaREVEAL_URLS_CSS:ntextContent-kautta), joten epäonnistunut luku ei lisää<style>-elementtiä eikä uudelleenyritys voi kahdentaa sitä. - Jos määrityksen lataus epäonnistuu jokaisella yrityksellä,
releaseBootstrappalauttaa merkin (vain tässä, ennen kuin merkintä alkaa), jotta myöhempi uudelleeninjektio voi yrittää uudelleen; kunstarton suoritettu, merkin on säilyttävä, koska tuore merkitsijä ei omistaisi edellisen läpikäynnin solmuja. - Kun
config.enabled, se kutsuustart-toimintoa, ja se rekisteröi ainaonConfigChanged-kuuntelijan, joka soveltaa määrityksen uudelleen (kattaen pois käytöstä→käytössä -siirtymän).
Aktiivisen sivustosäännön ratkaiseminen
start (ja applyConfig) kutsuu resolveSiteRule-toimintoa päättääkseen,
merkitäänkö — ja missä. resolveSiteRule kokeilee ensin suoritettavan asiakirjan
OMAA sijaintia resolveForHref-toiminnolla, joka suodattaa config.sites:n
käytössä oleviin sääntöihin ja delegoi ytimen selectMostSpecific-toiminnolle,
jotta tarkin täsmäys voittaa (tietty sisäänrakennettu voittaa laajan käyttäjän
jokerimerkin). Kun oma sijainti ei täsmää mihinkään JA asiakirjan oma alkuperä on
läpinäkymätön tai peritty — rajattu hasOpaqueOrigin-toiminnolla, tosi vain
about:blank/about:srcdoc:lle (peritty INHERITED_ORIGIN_URLS-kautta) tai
blob:/data::lle (OPAQUE_ORIGIN_SCHEMES) — se siirtyy varajärjestyksessä
YLIMPÄÄN kehykseen (readTopHref, sisäänrakennettu Proton-viesti-iframe-tapaus),
AVAAJA-ikkunaan (readOpenerHref, Outlookin ponnahdusikkunan lukuikkuna, joka
avataan window.open("about:blank")-kutsulla) ja lopulta
document.referrer-arvoon (readReferrer). Jokainen ulkoinen luku on suojattu
eri alkuperän pääsyä vastaan (mikä heittää poikkeuksen) ja tuottaa ehdokkaan
vain, kun se on luettavissa. TAVALLINEN täsmäämätön asiakirja on sitova eikä
koskaan peri sääntöä ympäröivästä kontekstista, jättäen ohjaimen toimettomaksi.
Sisältöjuureen rajaaminen ja muutosten tarkkailu
start tallentaa täsmänneen säännön contentRoot-valitsimen, luo merkitsijän
createAnnotator(config)-kautta ja käsittelee kunkin täsmänneen juuren
selectRoots(doc, contentRoot)-kutsusta (querySelectorAll, joka on kääritty
try/catch-rakenteeseen, jotta virheellinen valitsin epäonnistuu turvallisesti
arvoon []). Sitten se tarkkailee doc.body-elementtiä (vakaa säiliö)
OBSERVER_OPTIONS-asetuksilla ({ childList: true, subtree: true }).
Tarkkailija syöttää processMutations-toiminnon, joka rajaa jokaisen muutoksen
sisältöjuureen: se käsittelee uudelleen muutoksen target-kohteen vain, kun se
on Element juuren sisällä (isWithinRoot, closest-kautta), ja reitittää
kunkin lisätyn Elementin processAddedNode-toiminnon kautta, joka merkitsee
solmun, joka on juuren sisällä, ON juuri tai SISÄLTÄÄ sellaisen (laiska
SPA-kiinnitys). Joten juuren sisäinen kuhina ja myöhemmin kiinnittyvä
sisarviestin runko jäävät molemmat kiinni, kun taas juurten ulkopuolinen
sovelluskehys jätetään koskemattomaksi. Tyhjä merkitsijä (laajennus on
pysäytetty) on no-op.
applyConfig sovittaa elävän määritysmuutoksen: se purkaa, kun mikään sääntö ei
täsmää tai käytössä-lippu on pois; käynnistää uudelleen (palautus +
uudelleentarkkailu), kun ratkaistu contentRoot muuttui; ja muutoin uudelleenvirtaa
paikalla — annotator.setConfig ja sitten annotator.process kunkin täsmänneen
juuren yli — käynnistämättä tarkkailijaa uudelleen.
Yksittäisen ankkurin merkitseminen
Merkitsijä sijaitsee tiedostossa packages/core/src/linkProcessor.ts.
createAnnotator palauttaa Annotator-objektin sulkeumatilan yli, jota sivu ei
voi lukea: vahva Map, joka on avaimitettu ankkurin identiteetillä,
merkitsijäkohtainen data-ru-token generateToken-kautta
(crypto.getRandomValues) ja nykyinen määritys. Sen process(root)-metodi:
- Karsii rekisterimerkinnät, joiden ankkuri ei ole enää yhdistetty.
- Kerää ehdokasankkurit
collectAnchors-toiminnolla (jälkeläiseta[href]plus juuri itse, kun se ona[href], kahdennukset poistettuina). - Ratkaisee kunkin ankkurin kohteen
resolveAnnotatableUrl-toiminnolla, joka palauttaanull(ohita) tyhjälle/sivunsisäiselle hrefille, jäsentymättömälle hrefille, mille tahansa muulle mallille kuinhttp:/https:tai isännälle, joka on yhtä suuri taiignoreHosts-merkinnän alainen; se ratkaisee suhteelliset hrefitbaseURI-arvoa vasten, joten tulos on absoluuttinen. - Ottaa idempotentin nopean polun, kun
isAnnotationIntactvahvistaa, että hahmonnussormenjälki on muuttumaton ja jokainen luotu solmu on edelleen yhdistetty ja paikallaan; muutoin se palauttaa vanhentuneen merkinnän ja hahmontaa uudelleen. - Laskee täsmäämättömyyden
hostMismatch-toiminnolla puhtaalle ankkuritekstille, kunnioittaashowOnlyOnMismatch-asetusta (jättäen rehelliset linkit koskemattomiksi) ja kutsuuannotateAnchor.
annotateAnchor hahmontaa vain TURVALLISTA DOMia. "inline"-tilassa se lisää
eteen <span class="reveal-urls-url">-elementin, jonka URL asetetaan
textContent-kautta (etuliitteenä nuoli ja U+00A0 sitomaton välilyönti,
lyhennettynä truncateUrl-toiminnolla), jota seuraa <br>, jättäen linkin omat
lapset koskemattomiksi; yhdistetty span saa sitten kontrastitaustan
applyContrastBackdrop-kautta. "title"-tilassa se tallentaa alkuperäisen
title-arvon (TitleSave), ylikirjoittaa sen URL:llä — ainoa kirjoitus
ankkuriin — ja korostetun täsmäämättömyyden yhteydessä lisää sisar-
<span class="reveal-urls-warn">-merkin, joka kantaa REVEAL_URLS_WARN_LABEL-arvoa.
Jokainen luotu solmu merkitään data-ru="<token>"-attribuutilla (ei koskaan
ankkuri), ja kukin merkintä tallentaa configFingerprint-arvonsa, jotta
hahmonnusmäärityksen muutos pakottaa uudelleenvirtauksen. Värit ja fonttiohitukset
sovelletaan typitettyjen element.style.*-ominaisuuksien kautta
(applyColour/applyFontOverrides), ei koskaan interpoloituna CSS-merkkijonoon.
revertOwned poistaa luodut solmut viitteen perusteella ja palauttaa tallennetun
otsikon; revertAll tekee tämän jokaiselle omistetulle ankkurille ja tyhjentää
rekisterin.
truncateUrl toimii Unicode-KOODIPISTEIDEN yli ([...displayHref]), joten
leikkaus ei koskaan halkaise sijaisparia; se säilyttää mallin-ja-isännän
alkuperän ja lyhentää loput perässä olevalla URL_ELLIPSIS-arvolla.
Isännän täsmäämättömyys
packages/core/src/hostMismatch.ts hostMismatch vertaa linkin näkyvää tekstiä
sen href-arvoon rekisteröitävän verkkotunnuksen, ei raa'an isäntänimen
perusteella, joten rehellistä aliverkkotunnusta ei merkitä, kun taas samannäköinen
merkitään. extractHostCandidates jakaa tekstin ja pelkistää kunkin tokenin
hostCandidate-toiminnolla (joka vaatii pisteen ja tldts-tunnistetun
ICANN-julkisen päätteen, hyläten tavallisen pisteellisen tekstin kuten e.g).
Sekä href-isäntä että kukin ehdokas pelkistetään rekisteröitäväksi
verkkotunnukseksi tldts.getDomain-toiminnolla, ja MIKÄ TAHANSA ehdokas, jonka
verkkotunnus eroaa hrefin verkkotunnuksesta, tuottaa täsmäämättömyyden — joten
todellisen haitallisen isännän nimeäminen houkutusisännän rinnalla ei voi
vaimentaa varoitusta. Se ei koskaan heitä poikkeusta.
Tyylit ja kontrasti
packages/core/src/styles.ts sisältää injektoitavan REVEAL_URLS_CSS:n (kiinteä
asettelu, paksuus, muoto ja kiinteä valkoinen, täysin läpinäkymätön sirun tausta
— background: #ffffff, opacity: 1; värejä EI KOSKAAN interpoloida siihen) ja
ajonaikaiset applyColour/applyFontOverrides/applyContrastBackdrop-apurit.
applyContrastBackdrop lukee elementin ratkaistun värin ja ensimmäisen
läpinäkymättömän taustan, joka löytyy kävelemällä elementistä ITSESTÄÄN ylöspäin
(sen oma tausta — sirun kiinteä valkoinen oletus tai isäntäsivun tarkempi ohitus
— ennen mitään esivanhempaa) standardin getComputedStyle-kautta (lähteenä
element.ownerDocument?.defaultView, joten se on kehysturvallinen ja
tynkättävissä) ja, kun packages/core/src/contrast.ts needsWhiteBackdrop
raportoi, että teksti epäonnistuu WCAG AA:ssa (CONTRAST_THRESHOLD) kyseistä
taustaa vasten JA valkoinen todella auttaa, asettaa
style.backgroundColor = "white". contrast.ts tarjoaa parseColour,
relativeLuminance ja contrastRatio; se jäsentää rgb()/rgba()/heksamuodot,
jotka getComputedStyle palauttaa, ja käsittelee täysin läpinäkyvää väriä
"ei löytynyt".
Määritettävät isännät ja sisällön rajaaminen
SiteRule (packages/core/src/config.ts) sanoo MISSÄ merkintä suoritetaan
(match, allFrames) ja MIKÄ säiliö sen rajaa (contentRoot), plus enabled
ja builtin-lippu. Toimitettu DEFAULT_SITES kattaa Gmailin, Protonin
(allFrames-lipulla) ja molemmat Outlook-isännät. Config kokoaa globaalit
kytkimet, värit, fonttiohitukset ja sites-taulukon.
Täsmäyskaavamoottori
packages/core/src/matchPattern.ts sisältää tarkoituksella RAJOITTAVAN
lisäysaikaisen kieliopin MATCH_PATTERN (vain http/https, valinnainen alussa
oleva *.-isäntäjokerimerkki, glob-polku; ei porttia, ei *-mallia, ei
<all_urls>). matchesPattern täsmää URL:n validoitua kaavaa vasten, delegoiden
polun pathGlobMatches-toiminnolle (kukin * täsmää minkä tahansa
merkkijonon); se epäonnistuu turvallisesti arvoon false. parsePatternParts
jakaa validoidun kaavan osiin host/path/scheme/wildcardHost.
Päällekkäiset säännöt ratkeavat "tarkin voittaa" -periaatteella:
compareSiteSpecificity järjestää (1) tarkka isäntä jokeri-isännän edelle, (2)
pidempi literaali-isäntä, (3) literaalisempi polku (literalPathLength), sitten
(4) deterministinen ASCII-ratkaisu; selectMostSpecific palauttaa tarkimman
käytössä olevan säännön, joka täsmää URL:ään.
Myönnetyn alkuperän kattavuus on ERILLINEN, tarkoituksella LAAJEMPI täsmääjä.
permissions.getAll() voi raportoida myönnöt koko WebExtension-kieliopilla
(<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*), jotka
rajoittava MATCH_PATTERN hylkäisi. parseGrantedOrigin jäsentää nämä
GrantedOriginParts-muotoon, ja originCovers raportoi, kattaako myönnetty
alkuperä säännön match-arvon (esim. laaja https://*/* myöntö todella kattaa
jokaisen https-säännön). matchAllowsOriginFallback raportoi, onko säännön polku
tarkalleen ORIGIN_FALLBACK_PATH (/*) — Chromiumin ja Gecko/Firefox 128:n
rajoitusten turvallinen leikkaus dynaamisessa läpinäkymättömän alkuperän
varatoiminnossa.
Dynaaminen rekisteröinti
packages/webext/src/contentRegistration.ts rekisteröi yhden dynaamisen
sisältöskriptin kutakin käyttäjän lisäämää sääntöä kohden. Sisäänrakennettuja
palvelevat staattiset content_scripts-merkinnät, eikä niitä KOSKAAN rekisteröidä
dynaamisesti. desiredContentScripts suodattaa config.sites:n
ei-sisäänrakennettuihin, käytössä oleviin sääntöihin, joiden alkuperä
isOriginGranted (delegoiden originCovers-toiminnolle), kuvaa kunkin
RegisteredContentScript-objektiksi vakaalla id:llä (contentScriptId, matchin
FNV-1a-tiiviste, jotta id käyttää vain API-turvallista merkistöä) ja asettaa
matchOriginAsFallback: true VAIN, kun matchAllowsOriginFallback(rule.match)
pätee (/*-polku). Lipun rajaaminen tällä tavoin estää yhtä ei-yhteensopivan
polun sääntöä aiheuttamasta koko erärekisteröinnin hylkäystä; tällainen sääntö
rekisteröityy ilman ponnahduskattavuutta suunnitellusti.
reconcileContentScriptsOnce lukee myönnetyt alkuperät, tallennetun määrityksen
ja elävät rekisteröinnit, sitten suppenee OMINAISUUSTIETOISELLA erotuksella:
sameRegistration projisoi sekä halutun kuvaajan että elävän takaisinluvun
normalisoituun muotoon (soveltaen kunkin kentän todellista WebExtension-oletusta
— allFrames/matchOriginAsFallback oletus epätosi, persistAcrossSessions
oletus TOSI, runAt oletus document_idle), jotta tuoreesti rekisteröity
skripti vertautuu YHTÄ SUUREKSI sen tuottaneen kuvaajan kanssa eikä
uudelleenrekisteröintisilmukkaa voi syntyä. Koska updateContentScripts-toimintoa
ei ole, muuttunut skripti poisrekisteröidään ja rekisteröidään sitten uudelleen.
reconcileContentScripts kietoo tämän käynnissä olevaan lupaukseen (inFlight),
jotta kaksi lähes samanaikaista laukaisua (määritysmuutos JA
permissions.onAdded molemmat laukeavat sivuston lisäyksessä) sarjallistetaan
kilpailemisen sijaan. registerContentReconciliation johdottaa laukaisimet
(permissions.onAdded/onRemoved, onConfigChanged) ja suppenee kerran
käynnistyksessä; sen tuovat vain Chromen ja Firefoxin taustasisääntulopisteet.
Vapaaehtoinen lupaprosessi
Staattiset manifestit julistavat optional_host_permissions (http://*/*,
https://*/*), jotta käyttäjä voi myöntää mielivaltaisen lisäverkkoisännän
ajonaikaisesti. packages/webext/src/options/options.ts addSite validoi KOKO
ehdokassäännön (match JA sisältöjuuri) normaliseSiteRule-toiminnolla ENNEN
minkään luvan pyytämistä, johtaa isäntäalkuperän matchOrigin-toiminnolla ja
kutsuu browser.permissions.request-toimintoa Lisää-painikkeen napsautuseleen
sisältä; vain myönnön yhteydessä se lisää builtin:false-rivin ja
pysyväistallentaa kanonisen kirjoituspolun kautta.
Määritys, tallennus ja kanoninen malli
Yksittäinen validointisuppilo
normaliseConfig tiedostossa packages/core/src/config.ts on yksi kanoninen
lukupolku: se pakottaa ja rajaa jokaisen kentän, palaten oletukseen kaikessa
virheellisessä, eikä koskaan heitä poikkeusta. Se rakennetaan kenttäkohtaisista
validaattoreista — normaliseBoolean, normaliseRenderMode,
normaliseMaxLength, normaliseIgnoreHosts (pelkistäen kunkin merkinnän
paljaaksi punycode-isäntänimeksi bareHostname-kautta),
normaliseMatchColour/normaliseMismatchColour (rajattu safeColour.ts
isSafeColour-toiminnolla), normaliseCssSize, normaliseFontWeight,
normaliseContentRoot (rajattu
CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH-arvoilla),
normaliseMatchPattern (rajattu MATCH_PATTERN-arvolla) ja normaliseSites.
normaliseSites pudottaa hylkäykset, poistaa kahdennukset match-arvon
perusteella, PAKOTTAA builtin: true-arvon jokaiseen sääntöön, jonka match on
sama kuin sisäänrakennetun (sulkien tarkan-täsmäyksen-varjostaa-sisäänrakennetun
/ kaksoisinjektion vektorin), uudelleenkylvää minkä tahansa puuttuvan
sisäänrakennetun (jotta peukaloitu varasto ei voi pudottaa ydintarjoajaa) ja
järjestää aakkosnumeerisesti match-arvon perusteella kanonisen järjestyksen
vuoksi. configFingerprint tiivistää vain hahmonnukseen vaikuttavat kentät (pois
lukien enabled, ignoreHosts ja sites, jotka ohjaavat käynnistystä/pysäytystä
ja ohitusta uudelleenvirtauksen sijaan) fnv1a-kautta.
Tallennus
packages/webext/src/storage.ts kietoo WebExtension-tallennus-APIn. configArea
käyttää browser.storage.sync-tallennusta, kun se on saatavilla (jotta asetukset
vaeltavat), ja palaa browser.storage.local-tallennukseen (esim. Thunderbirdissä);
configAreaName raportoi, kumpi on aktiivinen. getConfig, setConfig ja
onConfigChanged kaikki suppiloivat raaka-arvonsa normaliseConfig-toiminnon
läpi, joten väärin muotoiltu tai peukaloitu varasto ei voi koskaan luovuttaa
virheellistä Config-objektia kutsujalle. onConfigChanged lisäksi ohittaa
tapahtumat EPÄAKTIIVISESTA tallennusalueesta ja muutokset asiaankuulumattomiin
avaimiin.
Kaksi lisäosaa käyttää uudelleen samaa luku -> normaliseConfig -> palautus
-suppiloa omien isäntävarastojensa yli. Outlook-tehtäväruudun
extensions/outlook/src/roamingStorage.ts kietoo
Office.context.roamingSettings-objektin (synkroninen get/set, vahvistettu
saveAsync-kautta); Gmail-lisäosan packages/gmail/src/propertiesStorage.ts
kietoo PropertiesService.getUserProperties()-objektin (käyttäjäkohtainen,
vaeltaa kyseisen käyttäjän laitteiden välillä, JSON-koodattu yhden avaimen alle
eikä tarvitse YHTÄÄN ylimääräistä OAuth-laajuutta — ei koskaan jaettua
ScriptProperties-objektia). Molemmat paljastavat getConfig/setConfig plus
korttirelevantit getIgnoreHosts/getHighlightMismatch-käyttäjät ja suojaavat
käytettävissä olemattoman isäntäpinnan erillisellä virheellä. Gmailin
kotisivulaukaisin hahmontaa CardService-asetuskortin getConfig-arvosta, ja
onSaveSettings-lomakelähetyskäsittelijä yhdistää jäsennetyn
ignoreHosts/highlightMismatch-arvon nykyiseen määritykseen ja
pysyväistallentaa sen setConfig-kautta; kontekstuaalinen laukaisin pujottaa
ignoreHosts-arvon sovittimeen ja highlightMismatch-arvon kortinrakentajaan,
lukien puolustavasti, jotta tallennusvirhe palautuu oletuksiin sen sijaan, että
rikkoisi viestianalyysin.
Kanoninen-määritys + kohdennettu-päivitys -malli
Kolme kirjoituspolkua lukee kanonisen määrityksen, muuttaa tarkalleen yhden asian ja kirjoittaa sen takaisin — ei koskaan vahvistaen tallentamattomia lomakemuokkauksia tai hahmontaen lomaketta uudelleen:
removeSite(options.ts): lukeegetConfig, pysyväistallentaasetConfig- toiminnollasites-taulukon yli, josta vain poistettu sääntö on suodatettu pois (pysyväistallenna ENSIN, sittenrow.remove(), sitten ehdollinenpermissions.remove, joka päätetään seuraavasta määrityksestä). Pysyväistallennuksen epäonnistuminen heittääSiteRemovalSaveError-poikkeuksen ja jättää DOMin koskemattomaksi; epäonnistunut peruutus heittääSitePermissionRevokeError-poikkeuksen sen jälkeen, kun rivi on jo poissa.addSite(options.ts): kuvattu yllä — validoi, pyydä lupa, lisää rivi, sittensaveOptions.toggleEnabled(options.ts): välitön pää-Ota käyttöön -kytkin kääntää VAINenabled-arvon tallennetussa määrityksessä ja kirjoittaa sen takaisin, hahmontamatta uudelleen (jotta käynnissä olevaa muokkausta ei koskaan ylikirjoiteta); epäonnistuneen kirjoituksen yhteydessä se palauttaa valintaruudun tallennettuun arvoon.
Työkalupalkin toiminnon oma toggleEnabled
(packages/webext/src/toolbarAction.ts) noudattaa samaa mallia taustapuolelta.
Moottorikohtainen koonti ja manifestisopimus
tooling/build.mjs ajaa yhden esbuild-koonnin jokaiselle WebExtension-kohteelle.
TARGETS-kuvaaja nimeää kunkin kohteen LÄHDEKOHTEEN: Chrome, Firefox ja
Thunderbird toimittavat oman src/-kansionsa; Edge, Opera ja Safari julistavat
chrome-kohteen lähteekseen; Outlook (Office.js) -lisäosa on oma lähteensä.
ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera ja Thunderbird — Safari ja Outlook
on jätetty pois) on se, mitä --all/--package kokoaa; Safari ja Outlook
koottavat vain, kun ne nimetään erikseen, ja Gmail (Apps Script) -lisäosa on
erillinen esbuild-niputus (make build-gmail) --all-silmukan ulkopuolella.
Tuo Gmail-niputus kohdistuu Apps Scriptin V8-ajoympäristöön, jolla ei ole
ES-moduuleja eikä natiivia URL-objektia: se rakennetaan ESM:nä ja sitten sen
perässä oleva export {…}-lause poistetaan (jotta laukaisinfunktiot pysyvät
ylätason globaaleina, joita Apps Script voi kutsua), ja se niputtaa pienen
URL-polyfillin (asennettu vain, kun URL puuttuu), jotta jaetun ytimen
new URL(...) ratkaisee linkit palvelinpuolella. buildTarget siivoaa
dist/<target>/-kansion, kopioi kohdekohtaisen manifest.json-tiedoston ja
icons/-kansion sanatarkasti, niputtaa kunkin lähdeskriptin IIFE:nä (injektoidut
INJECTED_SCRIPTS content.ts/messageDisplay.ts eivät voi olla ES-moduuleja,
ja taustat ovat myös klassisia työntekijöitä/tapahtumasivuja) ja jaetun
asetussivun ESM:nä sekä kopioi options.html/options.css kansiosta
packages/webext/src/options/. webextension-polyfill niputetaan jokaiseen
skriptiin.
tooling/version.mjs leimaa versiot: se ottaa MAJOR.MINOR-arvon juuren
package.json-tiedostosta ja lisää automaattisesti kasvavan BUILD-numeron
(luettu kohdemanifestien korkeimmasta olemassa olevasta kolmannesta
komponentista) jokaiseen kohdemanifestiin pitäen ne tahdissa. Useimmat kohteet
kantavat yhden manifest.json-tiedoston; Outlook-lisäosa kantaa kaksi versioitua
manifestia (manifest.json ja manifest.xml) ja molemmat leimataan, ja
_template-teline leimataan myös tahdissa. Juuren package.json pidetään samassa
versiossa, mutta pysyy validina semverinä (neliosainen versio sallitaan
manifestissa, ei koskaan package.json-tiedostossa). tooling/icons.mjs
(ajetaan make icons-kautta) rasteroi yksittäisen vektorilähteen assets/icon.svg
kunkin kohteen icons/icon48.png- ja icons/icon128.png-tiedostoiksi, kokeillen
cairosvg, sitten inkscape, sitten ImageMagick.
Manifestisopimus — tarkat vaaditut kentät moottoreittain — on yksi totuuden lähde
kussakin extensions/<engine>/test/manifest.smoke.test.mjs-tiedostossa (esim.
extensions/chrome/test/manifest.smoke.test.mjs-tiedoston assertManifestShape,
joka myös rakentaa kohteen ja vahvistaa, että jokainen viitattu tiedosto ratkeaa
distissä). Projektin kahdennuksenpoistokäytännön mukaisesti kenttäkohtaista
muotoa EI luetella uudelleen tässä; viittaa noihin savutesteihin ja
"Manifestisopimus"-huomautukseen
manuaalisessa testaussuunnitelmassa.
Kansainvälistäminen (i18n)
Reveal URLs on lokalisoitu monelle kielelle englannin lisäksi. Joukko on
yksilähteistetty tiedostoon packages/core/src/locales.json (SUPPORTED_LOCALES,
natiivinimet ja oletus), jonka sekä laajennusniput (ratkaistun JSON-tuonnin
kautta) että puhdas-Node-verkkosivuston koonti lukevat. Jokainen
ei-englanninkielinen merkkijono on konekäännetty ja odottaa ihmisen tarkistusta;
alkuperämerkintä on muotokohtainen (_locales käyttää kunkin viestin
description-kenttää, verkkosivuston kehyssanakirjat tallentavat sen tiedostoon
site/i18n/README.md, ja käännetyt dokumenttilähteet kantavat ensimmäisen rivin
HTML-kommenttia).
Lokalisointipintoja on kolme riippumatonta, tarkoituksella ei ristiinkytkettyä, ja kukin on jaettu koonti-aika / ajonaikainen -linjaa pitkin.
- Asetussivu (AD-1). Se toimittaa STAATTISTA englanninkielistä merkkausta;
jokainen käännettävä solmu kantaa
data-i18n="<key>"-merkintää (teksti) taidata-i18n-<attr>="<key>"-merkintää (attribuutti). Ajonaikaisestipackages/webext/src/options/locale.tson sovelluskerros: sefetchaa sivun oman paketoidun_locales/<code>/messages.json-tiedoston (runtime.getURL-kautta, eiweb_accessible_resources-tarvetta), kirjoittaa kunkin merkityn solmun uudelleen VAINtextContent-kautta (jotta lomakkeen<code>-esimerkkivihjeet pysyvät ehjinä) ja asettaa<html lang>-arvon. Sivu on TÄYSIN lokalisoitu: staattisen proosan rinnalla JS:n rakentamat sivustorivien tarrat ja Poista-painike kantavatdata-i18n-avainta (alustavallatranslatetulla tekstillä), jotta sama sovelluskerros uudelleenlokalisoi hahmonnetut rivit ilman uudelleenhahmonnusta, ja AIDOSTI dynaamiset merkkijonot — tilarivi, sivustonlisäyspalaute ja versiorivi — hahmontuvatlocale.ts:ntranslate(<key>)-kautta (currentMessages → englantilainen vara → avain) kutsuhetkellä. Englantilainen vara on_locales/en/messages.json-luettelo, joka on TUOTU (niputettu)options.js-tiedostoon koonti-aikana, jotentranslateratkeaa luettavaksi englanniksi SYNKRONISESTI moduulin latauksesta — dynaaminen merkkijono, joka hahmontuu ennen kuin valitsin asettuu, tai sen jälkeen kun ajonaikainen luettelohaku epäonnistuu, ei koskaan rappeudu raa'aksi viestiavaimeksi.locale.tsjulkaisee sovelletun viestikartan ja laukaiseeonLocaleChange-kuuntelijansa jokaisen soveltamisen jälkeen, jotenoptions.tsuudelleenhahmontaa parhaillaan näytetyn tilan/palautteen/version ja uudelleenkohdistaa Online-käyttöopas -linkin jokaisella vaihdolla. Versiorivi käyttääoptionsVersion-viestiä, jonka{version}-paikkamerkki korvataan koontinumerolla (token käännetty, numero sanatarkasti). Käyttöopaslinkki seuraa aktiivista kieltä sen dokumenttivarianttiin (AD-9): englanti pitää ylätasonmanual.html-tiedoston, jokainen muu kieli avaanl/handleiding.html-tiedoston säilyttäen?v=-versioleiman. Aktiivinen kieli on käyttäjän tallentama valinta tai sen puuttuessa selaimen kieli, joka on ratkaistu tuettuun peruskieleen ytimenresolveLocale-toiminnolla (AD-5); tukematon tai epäonnistunut kieli jättää esihahmonnetun englannin (jatranslaterappeutuu sitten englantilaiseen varaan). Käyttöoppaan Näyttökieli-valitsin pysyväistallentaa valintansa erilliseenuiLocale-avaimeenstorage.local-tallennuksessa — ei koskaan synkronoidussaConfig-objektissa, joten se on laitekohtainen eikä koskaan vaella. - Verkkosivuston kehys (AD-2). Staattisen sivuston generaattori
tooling/site.mjspalvelinhahmontaa jokaisen kehyssolmun englanniksi merkiten sendata-i18n-attribuutilla ja pitää<html lang="en-GB">-arvon (AD-4). Selainpuolen lataajasite/scripts/i18n.mjs(riippuvuudeton ES-moduuli) vaihtaa sitten kehyksen vierailijan kielelle etusijajärjestyksen mukaan — pysyväistallennettulocalStorage-valinta (revealUrlsSiteLocale) → ensimmäinen tuettunavigator.languages-perustäsmäys (AD-5) → englanti, hakiensite/i18n/<code>.json-tiedoston. Toisin kuin asetusten sovelluskerros, se vaihtaainnerHTML-kautta, koska sivuston kehysarvot kantavat luotettua sisäistä merkkausta (<code>,<a>); pariteettitesti naulaa jokaisen ei-englanninkielisen arvon tagi-/attribuutti-/URL-sekvenssin tavutarkasti englantia vastaavaksi, jotta käännös ei voi koskaan pudottaa linkkiä tai rikkoa tagia. Puuttuva avain pitää esihahmonnetun englanninkielisen tilannekuvan; epäonnistunut haku jättää englanninkielisen sivun ehjäksi (siro rappeutuminen, ei koskaan tyhjä). - Dokumentaatiosivut (AD-8/AD-9). Dokumenttisivut hahmonnetaan
KIELIKOHTAISESTI koonti-aikana, ei ajonaikaisesti vaihdettuna. Englanninkieliset
dokumentit pysyvät ylätason poluissa; jokainen muu kieli saa kopion
<code>/-kansion alle, käännetystädocs/<code>/<DOC>.md-tiedostosta kun se on saatavilla, muutoin englanninkielisestä lähteestä — sivukohtainen vara, jonkabuildSiteLOKITTAA hiljaisen poisjättämisen sijaan. Dokumentin RUNKO ei kannadata-i18n-attribuuttia (se on raa'asti hahmonnettua markdownia), joten ajonaikainen kehyslataaja ei koskaan kosketa sitä; vain jaetut kehyssolmut vaihtuvat. Dokumenttisivun kielenvalitsin NAVIGOI sisarkielen sivulle sen sijaan, että vaihtaisi paikalla. - Sisäisten linkkien lokalisointi (Finding 2). Valittu kieli kantautuu
dokumenttinavigoinnin yli. Jokainen SISÄINEN dokumenttiankkuri (viisi sivua
architecture.html,index.html,licence.html,manual.html,privacy.html) merkitäändata-doc-link="<page>"-attribuutilla generaattorin toimesta, ja JavaScriptittömässä tapauksessa sen href ratkeaa jo NYKYISEN sivun omaan kielivarianttiin (navDocHref— kolme kielikohtaista dokumenttisivua lokalisoituvat; vain-englantiindex.html/licence.htmlpysyvät ylätasolla). Ajonaikaisen lataajanrewriteDocLinksuudelleenkohdistaa sitten jokaisen sisäisen dokumenttiankkurin — ne merkityt JA minkä tahansa luettelon injektoiman tavallisen ankkurin kutenmanual.html#installing— AKTIIVISEN kielen varianttiin, säilyttäen minkä tahansa#hash/?query-arvon eikä koskaan koskien ulkoisiin linkkeihin. Dokumenttisivulla TALLENNETTU valinta, joka eroaa sivun omasta kielestä, NAVIGOI vastaavaan varianttiin; silmukkavartija laukeaa vain eksplisiittisellä tallennetulla valinnalla (ei koskaan pelkällä selainkielen mieltymyksellä) ja vain, kun kohde eroaa jo näytetystä sivusta. Luettelot itse pitävät englanninkieliset sisäiset URL:nsä, joten pariteettitesti pysyy vihreänä — lokalisointi on puhdas ajonaikainen läpikäynti DOMin yli.
Sähköpostiohjelman "Aktiiviset sivustot" -vaimennus (AD-3)
Sähköpostiohjelmakohde (Thunderbird tänään) näkee jo jokaisen hahmonnetun
viestin, joten isäntäkohtainen "Aktiiviset sivustot" -editori on siellä
tarpeeton ja poistetaan. Päätöstä kantaa dokumentoitu kohdekohtainen
kuvaajalippu, mailClient: true, tooling/build.mjs:n TARGETS.thunderbird-
kohteessa — ei koskaan kovakoodattu kohdenimi koonti- tai käyttöliittymälogiikassa
— ja se toteutetaan kahdessa puolikkaassa:
- Koonti-aika.
copyAssetsperustuudescriptorFor(target).mailClient-arvoon: sähköpostiohjelmakohteelle se ajaa puhtaanremoveSitesSection(html)-muunnoksen (joka riisuu koko<section class="sites">-isäntäeditorin, mukaan lukien jokaisen sivustonlisäyssäätimen, ja heittääMissingSourceError-poikkeuksen, jos osio puuttuu, jotta merkkausmuutos ei voi koskaan hiljaa toimittaa editoria) ja kirjoittaa tuloksen; selainkohteet saavatoptions.html-tiedoston tavutarkasticpSync-kautta. - Ajonaikainen. Niputettu ohjain
packages/webext/src/options/options.tssietää puuttuvan osion:renderSites/readSites/applyConfig/readFormluotaavat#sites-list-elementtiä ja no-op'aavat sen sijaan, että heittäisivätOptionsFieldMissingError-poikkeuksen, jareadFormJÄTTÄÄ POISsites-avaimen, kun osio on poissa. Koskasites-avaimen pois jättäminen antaisinormaliseConfig-toiminnon uudelleenkylvää sisäänrakennetut oletukset ja pudottaa käyttäjän sivustot,saveOptionspalauttaa tallennetutsites-arvot ennen normalisointia aina kun osio on poissa — se on se, mikä saa sähköpostiohjelman tallennuksen säilyttämään määritetyt isännät. Ajonaikainen vartija,guardMailClientSites, poistaa osion, jos se on edelleen läsnä, tunnistaen sähköpostiohjelman API:n/ominaisuuden läsnäolosta (messages/messageDisplay/scripting.messageDisplayinjektoidussabrowser-objektissa), ei koskaan kohdenimestä; se ajetaan ensininitOptions-toiminnossa, jotta myöhempi lisäys-/luettelojohdotus ohittaa luonnollisesti puuttuvat elementit. Selainajoympäristössä vartija on no-op.
Tietoturva- ja tietosuoja-asento
- Ei verkkoa tai eksfiltraatiota. Mikään moduuli ei suorita
fetch/XHR- kutsua tai mitään muuta verkkokutsua; laajennus lukee vain tallennetun määrityksen ja sivun DOMin. - Vain turvallinen DOM. Merkintä ja injektoitu tyylitiedosto käyttävät
createElement/textContent/typitettyjästyle.*-ominaisuuksia yksinomaan, ei koskaaninnerHTML/insertAdjacentHTML/eval. Ytimen osalta tämän valvoopackages/core/test/purity.test.ts. - Kaikki ulkoinen/määritys-syöte normalisoitu. Jokainen luku ja kirjoitus
suppiloituu
normaliseConfig-toiminnon läpi; värit läpäisevätisSafeColour-tarkistuksen; täsmäyskaavat läpäisevätMATCH_PATTERN- tarkistuksen; sisältöjuurivalitsimet on rajattuCONTENT_ROOT_PATTERN-arvolla ja annetaan vainquerySelectorAll/closest-toiminnoille, ei koskaan interpoloituna merkkaukseen tai CSS:ään. - Pienin etuoikeus. Sisäänrakennetut webmail-alkuperät ovat kiinteät
host_permissions; lisäisännät ovat vapaaehtoisiaoptional_host_permissions-kautta ja eleeseen sidotullapermissions.request-kutsulla. Työkalupalkin toiminto ei julista ponnahdusikkunaa. - Gecko-tiedonkeruu. Firefoxin ja Thunderbirdin manifestit julistavat
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Lisenssi. Projekti on AGPL-3.0-only (
LICENSE,package.json).
Testaus
- Vitest-yksikkötestit kattavat molemmat paketit:
packages/core/test/*(määritys, kontrasti, isännän täsmäämättömyys, linkkiprosessori, tyylit ja puhtausvartija) japackages/webext/test/*(sisältö, tallennus, asetukset, rekisteröinti, työkalupalkki, viestinäyttö ja mukautettu sivuston päästä-päähän-testi). Kukin paketti ajaavitest run. - Cucumber BDD -skenaariot sijaitsevat
features/-kansiossa (johdotettucucumber.json-tiedostolla) ja ajavat todelliset@reveal-urls/webext-moduulit uskollisten testikaksosten kautta tiedostossafeatures/support/webext.ts— etenkinnormaliseReadBack, joka peilaa moottorin JÄTTÄEN POIS epätosi-arvoiset liput takaisinluvussa, jotta lepsu testivaljas ei voi piilottaa uudelleenrekisteröintisilmukkaa — jsdom-asiakirjan yli, joka on pystytetty tiedostossafeatures/support/world.ts. - Kohdekohtaiset manifestisavutestit
(
extensions/<engine>/test/manifest.smoke.test.mjs, Noden sisäänrakennettunode:test) toteavat kunkin manifestin tarkan muodon ja että rakennettu dist ratkaisee jokaisen viitatun tiedoston.tooling/test/lisää koonti-, versio- ja valmiuden-määritelmä-testit. - CI (
.github/workflows/ci.yml) portittaa jokaisen työnnön ja vetopyynnönmake test-,make bdd- jamake lint-komentoihin (ajettu suoraan ajurissa, ohittaen Makefilen Docker-RUN/IMAGE_DEP). Versioleimaus ja paketointi on tarkoituksella jätetty pois; ne sijaitsevat erillisessä, tagilla laukaistussa.github/workflows/release.yml-tiedostossa.