test(m1): seed anonymised fixture corpus with hygiene scan

This commit is contained in:
Kjell Tore Guttormsen 2026-09-04 19:36:06 +02:00
commit b538b5c5f0
17 changed files with 910 additions and 0 deletions

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-01
arbeidsgiver: Fjordtek AS
orgnr: 812345673
rolle: Seniorraadgiver datadeling
kilde: eksempelportalen
url: https://jobb.fjordtek.example/ann-01
kontakt: rekruttering@fjordtek.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Tromsø
reisetid_min: 20
hjemmekontor_dager: 3
ansettelsesform: fast
lonn_nok: 900000
senioritet: senior
turnus: nei
delscore:
fagomrade: 8
oppgavetype: 7
teknologi: 6
selskapstype: 5
sum: 26
---
## Om stillingen
Fjordtek AS soeker en seniorraadgiver datadeling i Tromsø. Arbeidsstedet er Tromsø, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Passerer alle hardfiltrene i 01-gyldig.md.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-02
arbeidsgiver: Kystdata AS
orgnr: 912345611
rolle: Raadgiver datadeling
kilde: eksempelportalen
url: https://jobb.kystdata.example/ann-02
kontakt: rekruttering@kystdata.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Bodø
reisetid_min: 25
hjemmekontor_dager: 2
ansettelsesform: fast
lonn_nok: 700000
senioritet: senior
turnus: nei
delscore:
fagomrade: 8
oppgavetype: 7
teknologi: 6
selskapstype: 4
sum: 25
---
## Om stillingen
Kystdata AS soeker en raadgiver datadeling i Bodø. Arbeidsstedet er Bodø, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Faller paa lonn: 700000 er under gulvet paa 850000.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-03
arbeidsgiver: Nordvik kommune
orgnr: 811099105
rolle: Fagleder arkitektur
kilde: eksempelportalen
url: https://jobb.nordvik.example/ann-03
kontakt: rekruttering@nordvik.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Målselv
reisetid_min: 90
hjemmekontor_dager: 2
ansettelsesform: fast
lonn_nok: 950000
senioritet: senior
turnus: nei
delscore:
fagomrade: 7
oppgavetype: 6
teknologi: 5
selskapstype: 6
sum: 24
---
## Om stillingen
Nordvik kommune soeker en fagleder arkitektur i Målselv. Arbeidsstedet er Målselv, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Faller paa geografi: 90 minutter mot et tak paa 45.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-04
arbeidsgiver: Eksempel AS
orgnr: 823456787
rolle: Losningsarkitekt
kilde: eksempelportalen
url: https://jobb.eksempel.example/ann-04
kontakt: rekruttering@eksempel.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Ålesund
reisetid_min: 30
hjemmekontor_dager: 0
ansettelsesform: fast
lonn_nok: 1050000
senioritet: leder
turnus: nei
delscore:
fagomrade: 6
oppgavetype: 8
teknologi: 7
selskapstype: 5
sum: 26
---
## Om stillingen
Eksempel AS soeker en losningsarkitekt i Ålesund. Arbeidsstedet er Ålesund, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Faller paa arbeidsform: null hjemmekontordager mot et krav om to.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-05
arbeidsgiver: Fjordtek AS
orgnr: 812345673
rolle: Seniorraadgiver, vikariat
kilde: eksempelportalen
url: https://jobb.fjordtek.example/ann-05
kontakt: rekruttering@fjordtek.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Bergen
reisetid_min: 15
hjemmekontor_dager: 3
ansettelsesform: vikariat
lonn_nok: 780000
senioritet: senior
turnus: nei
delscore:
fagomrade: 8
oppgavetype: 6
teknologi: 6
selskapstype: 5
sum: 25
---
## Om stillingen
Fjordtek AS soeker en seniorraadgiver, vikariat i Bergen. Arbeidsstedet er Bergen, og
stillingen er vikariat.
## Hvorfor denne annonsen ligger i korpuset
Faller paa ansettelsesform: vikariat staar paa avviser-lista.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-06
arbeidsgiver: Kystdata AS
orgnr: 912345611
rolle: Driftsleder vaktsentral
kilde: eksempelportalen
url: https://jobb.kystdata.example/ann-06
kontakt: rekruttering@kystdata.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Trondheim
reisetid_min: 20
hjemmekontor_dager: 1
ansettelsesform: fast
lonn_nok: 860000
senioritet: senior
turnus: ja
delscore:
fagomrade: 5
oppgavetype: 3
teknologi: 4
selskapstype: 4
sum: 16
---
## Om stillingen
Kystdata AS soeker en driftsleder vaktsentral i Trondheim. Arbeidsstedet er Trondheim, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Faller paa absolutte_nei: turnus.

View file

@ -0,0 +1,38 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-07
arbeidsgiver: Nordvik kommune
orgnr: 811099105
rolle: Raadgiver, nyutdannet
kilde: eksempelportalen
url: https://jobb.nordvik.example/ann-07
kontakt: rekruttering@nordvik.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Kirkenes
reisetid_min: 35
hjemmekontor_dager: 2
ansettelsesform: fast
lonn_nok: 660000
senioritet: junior
turnus: nei
delscore:
fagomrade: 6
oppgavetype: 5
teknologi: 5
selskapstype: 4
sum: 20
---
## Om stillingen
Nordvik kommune soeker en raadgiver, nyutdannet i Kirkenes. Arbeidsstedet er Kirkenes, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Faller paa senioritet: junior mot et gulv paa senior.

View file

@ -0,0 +1,37 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte annonse og ingen
# ekte arbeidsgiver. Organisasjonsnummeret feiler mod-11 med vilje, og
# vertsnavn og e-postdomener ligger under .example (RFC 2606).
# Datoer er relative til 2026-09-15. Delscore er registrert her slik at
# regnestykket i steg 10 kan testes uten et modellkall.
annonse_id: ann-08
arbeidsgiver: Eksempel AS
orgnr: 823456787
rolle: Fagansvarlig datadeling
kilde: eksempelportalen
url: https://jobb.eksempel.example/ann-08
kontakt: rekruttering@eksempel.example
publisert: 2026-09-08
frist: 2026-09-29
sted: Stavanger
reisetid_min: 25
hjemmekontor_dager: 3
ansettelsesform: fast
senioritet: senior
turnus: nei
delscore:
fagomrade: 7
oppgavetype: 7
teknologi: 6
selskapstype: 5
sum: 25
---
## Om stillingen
Eksempel AS soeker en fagansvarlig datadeling i Stavanger. Arbeidsstedet er Stavanger, og
stillingen er fast.
## Hvorfor denne annonsen ligger i korpuset
Lonn er ikke oppgitt. Ukjent er ikke det samme som under gulvet, og hardfilteret skal ikke behandle det som et avslag.

58
tests/fixtures/profiles/01-gyldig.md vendored Normal file
View file

@ -0,0 +1,58 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Tromsø
maks_reisetid_min: 45
hjemmekontor_min_dager: 2
vurderer_flytting: nei
lonn:
gulv_nok: 850000
onsket_nok: 950000
kommentar: fast stilling, ikke konsulentpaaslag
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
---
## Kjerne
Raadgiver som faar datadeling til aa skje i praksis.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,57 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Tromsø
maks_reisetid_min: 45
hjemmekontor_min_dager: 2
vurderer_flytting: nei
lonn:
onsket_nok: 950000
kommentar: aapen for diskusjon
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
---
## Kjerne
Samme profil, men uten et gulv aa filtrere paa.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,53 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
lonn:
gulv_nok: 850000
onsket_nok: 950000
kommentar: fast stilling, ikke konsulentpaaslag
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
---
## Kjerne
Profil uten geografiblokk; reisetid kan ikke hardfiltreres.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,59 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Tromsø
maks_reisetid_min: 45
hjemmekontor_min_dager: 2
vurderer_flytting: nei
lonn:
gulv_nok: 850000
onsket_nok: 950000
kommentar: fast stilling, ikke konsulentpaaslag
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
favorittfarge: bla
---
## Kjerne
Profil med en noekkel kontrakten i seksjon 5.1 ikke kjenner.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,63 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Tromsø
maks_reisetid_min: 45
hjemmekontor_min_dager: 2
vurderer_flytting: nei
lonn:
gulv_nok: 850000
onsket_nok: 950000
kommentar: fast stilling, ikke konsulentpaaslag
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
vekter:
fagomrade: 4
oppgavetype: 3
teknologi: 2
selskapstype: 1
---
## Kjerne
Profil som oppgir vekter selv, i stedet for aa arve standardvektene.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,58 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Bergen
maks_reisetid_min: 30
hjemmekontor_min_dager: 1
vurderer_flytting: ja
lonn:
gulv_nok: 720000
onsket_nok: 800000
kommentar: aapen
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
---
## Kjerne
Profil uten vekter i det hele tatt; scoringen maa falle tilbake.
## Kompetanse
Dybde: datadeling i offentlig sektor -- ledet innfoeringen av et felles
API-register (belegg: Signaturprosjekt 1). Bredde: innkjoep, arkitektur.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar med reell beslutningsmyndighet. Bort fra: rene bestillerroller.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register og avtaleverk.
Maalbart utfall: 11 av 14 tjenester tok det i bruk paa ett aar. Dekker krav om
arkitektur og gjennomfoering.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse i stor skala | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det faktisk tas i bruk, ikke bare leveres."
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.

View file

@ -0,0 +1,56 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Tromsø
maks_reisetid_min: 45
hjemmekontor_min_dager: 2
vurderer_flytting: nei
lonn:
gulv_nok: 850000
onsket_nok: 950000
kommentar: fast stilling, ikke konsulentpaaslag
ansettelsesform:
aksepterer: fast
avviser: vikariat
absolutte_nei:
- turnus
- rendyrket salg
senioritet:
min: senior
maks: leder
---
## Profilgap
Mangler erfaring med anskaffelser over EOES-terskel.
## Kjerne
Raadgiver som faar datadeling til aa skje i praksis.
## Signaturprosjekter
Situasjon: fire etater delte ikke data. Bidrag: felles register. Maalbart
utfall: 11 av 14 tjenester tok det i bruk. Dekker krav om arkitektur.
## Kompetanse
Dybde: datadeling (belegg: Signaturprosjekt 1). Bredde: innkjoep.
Under oppbygging: maskinlaering i drift.
## Retning
Mot: fagansvar. Bort fra: rene bestillerroller.
## Kjente svakheter
| svakhet | kompensasjon | tiltak |
| --- | --- | --- |
| lite linjeledelse | prosjektledelse | soeker teamlead-ansvar |
## Formuleringer som virker
"Jeg tar ansvar for at det tas i bruk."

View file

@ -0,0 +1,14 @@
---
# Proveniens: syntetisk testdata for jobbsok. Ingen ekte person, ingen ekte
# arbeidsgiver, ingen ekte annonse. Organisasjonsnumre er konstruert slik at
# de FEILER mod-11-kontrollen. Alle datoer er relative til 2026-09-15.
oppdatert: 2026-09-15
geografi:
base: Bodø
lonn:
gulv_nok: 850000
---
## Kjerne
Denne profilen skal ikke parse. Tabulator i innrykk, med vilje.

View file

@ -0,0 +1,189 @@
"""The fixture corpus is anonymised, and this test is what makes that a fact
rather than an intention (plan Step 7).
This repository has a PUBLIC remote. A fixture carrying the operator's real
name, home address, employer or a real job-board URL would be published the
moment it is pushed, and no later cleanup un-publishes it. So the corpus is
scanned on every run, before it grows past the point where anyone reads it
whole.
The scan reports its denominator, and that is the point rather than a
courtesy. "Found nothing" is a measurement, and a measurement without a
denominator is indistinguishable from a scan that ran over an empty
directory, matched nothing because its patterns were wrong, or never ran at
all. So every run says how many files and how many bytes it looked at, and
`test_the_scan_can_actually_find` seeds a canary into a temporary corpus and
requires the scan to catch it -- a scanner that cannot find is not evidence of
absence.
Two deliberate deviations from the plan text, both stated rather than
smuggled:
1. The operator's home address is not hard-coded here. Writing a real home
address into a deny list on a public remote would publish the very thing
the list exists to keep out. Private tokens go in the environment variable
`JOBBSOK_HYGIENE_DENY_EXTRA` (newline-separated) instead, and the scan
picks them up if it is set. The name and employer ARE listed literally,
because both already appear in every commit's author metadata on the same
public remote -- listing them costs nothing that is not already public and
buys a check that actually runs.
2. The provenance-header rule covers the directories this step seeds.
`cowork-probe/` is exempt by name: it is the Step 1 probe rig, machine
config with no person in it, and it predates this rule.
Style note: this file follows tests/test_helpers_selfcheck.py.
"""
import os
import re
import warnings
FIXTURES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures")
#: Directories seeded by this step, and the ones the provenance rule governs.
CORPUS_DIRS = ("profiles", "listings")
#: Exempt from the provenance rule, with the reason in the module docstring.
PROVENANCE_EXEMPT = ("cowork-probe",)
#: Literal tokens that must never appear. Real employer names and real job
#: boards; the person tokens are already public in the commit metadata.
DENY_TOKENS = (
"Kjell Tore",
"Guttormsen",
"Statens vegvesen",
"vegvesen",
"finn.no",
"arbeidsplassen.nav.no",
"jobbnorge.no",
"linkedin.com",
"indeed.com",
)
#: Extra deny tokens supplied at run time, one per line. This is where a home
#: address belongs -- checked, never committed.
DENY_EXTRA_ENV = "JOBBSOK_HYGIENE_DENY_EXTRA"
#: Every host and mail domain in the corpus must sit under the reserved
#: `.example` TLD (RFC 2606), so nothing in here can resolve to a real place.
SAFE_TLD = ".example"
EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,})")
URL_HOST_RE = re.compile(r"https?://([A-Za-z0-9.:-]+)")
PROVENANCE_MARKER = "Proveniens:"
class KorpusSkannet(UserWarning):
"""Not a defect: the denominator the hygiene scan actually looked at.
Emitted as a warning because pytest shows warnings even under ``-q``,
while a passing test's stdout is captured and never reaches the operator.
"""
def deny_tokens():
tokens = list(DENY_TOKENS)
extra = os.environ.get(DENY_EXTRA_ENV, "")
tokens.extend(line.strip() for line in extra.splitlines() if line.strip())
return tokens
def corpus_files(root):
found = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames.sort()
for name in sorted(filenames):
found.append(os.path.join(dirpath, name))
return found
def scan(root):
"""Scan every file under ``root`` and report findings AND the denominator."""
tokens = deny_tokens()
findings = []
files = corpus_files(root)
total_bytes = 0
for path in files:
with open(path, "r", encoding="utf-8") as handle:
text = handle.read()
total_bytes += len(text.encode("utf-8"))
relative = os.path.relpath(path, root)
lowered = text.lower()
for token in tokens:
if token.lower() in lowered:
findings.append((relative, "deny-token %r" % token))
for domain in EMAIL_RE.findall(text):
if not domain.lower().endswith(SAFE_TLD):
findings.append((relative, "mail domain %r is not %s" % (domain, SAFE_TLD)))
for host in URL_HOST_RE.findall(text):
if not host.lower().split(":")[0].endswith(SAFE_TLD):
findings.append((relative, "url host %r is not %s" % (host, SAFE_TLD)))
return {"files": len(files), "bytes": total_bytes, "findings": findings}
def report(result, root):
message = "hygiene scan: %d files, %d bytes scanned under %s; %d finding(s)" % (
result["files"],
result["bytes"],
os.path.basename(root.rstrip(os.sep)),
len(result["findings"]),
)
print(message)
warnings.warn(message, KorpusSkannet)
return message
def test_every_corpus_fixture_carries_a_provenance_header():
checked = 0
for name in CORPUS_DIRS:
directory = os.path.join(FIXTURES, name)
assert os.path.isdir(directory), "corpus directory %r is missing" % name
files = corpus_files(directory)
assert files, "corpus directory %r is empty" % name
for path in files:
with open(path, "r", encoding="utf-8") as handle:
text = handle.read()
assert PROVENANCE_MARKER in text, "%s has no %r header" % (path, PROVENANCE_MARKER)
checked += 1
# Eight profiles and eight listings, per the plan. A rule that checked
# zero files would pass just as quietly.
assert checked == 16, "expected 16 corpus fixtures, provenance-checked %d" % checked
def test_no_deny_listed_token_appears_anywhere_in_the_corpus():
result = scan(FIXTURES)
assert result["findings"] == [], "hygiene findings: %r" % (result["findings"],)
def test_the_scan_states_the_denominator_it_looked_at():
result = scan(FIXTURES)
message = report(result, FIXTURES)
assert result["files"] >= 16
assert result["bytes"] > 0
assert str(result["files"]) in message and "bytes scanned" in message
def test_the_scan_can_actually_find(tmp_path):
# A canary corpus, deliberately dirty. It lives in tmp_path and never in
# tests/fixtures, because a canary committed to a public remote is the
# leak it is supposed to warn about.
(tmp_path / "cv.md").write_text(
"Proveniens: kanarifugl.\nTidligere hos Statens vegvesen.\n", encoding="utf-8"
)
(tmp_path / "kontakt.md").write_text(
"Proveniens: kanarifugl.\nSkriv til rekruttering@fjordtek.no\n", encoding="utf-8"
)
(tmp_path / "lenke.md").write_text(
"Proveniens: kanarifugl.\nSe https://www.finn.no/job/123\n", encoding="utf-8"
)
result = scan(str(tmp_path))
reasons = " ".join(reason for _path, reason in result["findings"])
assert result["files"] == 3
assert "vegvesen" in reasons
assert "fjordtek.no" in reasons
assert "finn.no" in reasons