Fix audit HIGHs/MEDs in build code
- ArchiveIndex: guard rawIndex/rawState with doesFileExist so a fresh clone (gitignored data/ JSONs absent) degrades to empty instead of crashing — the behavior the module doc already promised (AUDIT §1.2) - Commonplace: decode YAML via encodeUtf8, not Char8.pack, which truncates codepoints above 0x7F (AUDIT §3.2) - Stats: DayOfWeek is ISO-numbered (Mon=1..Sun=7); dowOf and weekStart assumed Mon=0..Sun=6, clipping every Sunday cell outside the heatmap viewBox and starting weeks on Sunday (AUDIT §3.1) - Site: epistemicEntry now honors the proved/proven confidence sentinel like Contexts.overallScoreField (AUDIT §2.6) - Contexts: affiliationField returns noResult instead of an empty list, so essays without affiliation no longer render an empty meta row (AUDIT §2.7) Verified: full site build passes; proved page gets score=100 in epistemic-meta.json; empty .meta-affiliation gone; heatmap rows y=22..94 all inside the 104-high viewBox. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
70ad44e9f4
commit
7ca937d98c
|
|
@ -132,18 +132,26 @@ activeUrls = unsafePerformIO $ do
|
|||
{-# NOINLINE rawIndex #-}
|
||||
rawIndex :: Map Text IdxEntry
|
||||
rawIndex = unsafePerformIO $ do
|
||||
decoded <- A.eitherDecodeFileStrict' indexPath
|
||||
let parsed = either (const Map.empty) id decoded
|
||||
return $ Map.filterWithKey
|
||||
(\canon _ -> normalizeUrl canon `Set.member` activeUrls)
|
||||
parsed
|
||||
exists <- doesFileExist indexPath
|
||||
if not exists
|
||||
then return Map.empty
|
||||
else do
|
||||
decoded <- A.eitherDecodeFileStrict' indexPath
|
||||
let parsed = either (const Map.empty) id decoded
|
||||
return $ Map.filterWithKey
|
||||
(\canon _ -> normalizeUrl canon `Set.member` activeUrls)
|
||||
parsed
|
||||
|
||||
-- | @url -> status@. Absent/malformed file -> empty (every entry 'Live').
|
||||
{-# NOINLINE rawState #-}
|
||||
rawState :: Map Text ArchiveStatus
|
||||
rawState = unsafePerformIO $ do
|
||||
decoded <- A.eitherDecodeFileStrict' statePath
|
||||
return $ either (const Map.empty) (Map.map seStatus) decoded
|
||||
exists <- doesFileExist statePath
|
||||
if not exists
|
||||
then return Map.empty
|
||||
else do
|
||||
decoded <- A.eitherDecodeFileStrict' statePath
|
||||
return $ either (const Map.empty) (Map.map seStatus) decoded
|
||||
|
||||
-- | @normalised-url -> slug@: the canonical key and every alias from
|
||||
-- @archive-index.json@, each fed through 'normalizeUrl'. Both keys and
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ module Commonplace
|
|||
import Data.Aeson (FromJSON (..), withObject, (.:), (.:?), (.!=))
|
||||
import Data.List (nub, sortBy)
|
||||
import Data.Ord (comparing, Down (..))
|
||||
import qualified Data.ByteString.Char8 as BS
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import qualified Data.Yaml as Y
|
||||
import Hakyll hiding (escapeHtml, renderTags)
|
||||
import Contexts (siteCtx)
|
||||
|
|
@ -140,7 +141,10 @@ loadCommonplace :: Compiler [CPEntry]
|
|||
loadCommonplace = do
|
||||
rawItem <- load (fromFilePath "data/commonplace.yaml") :: Compiler (Item String)
|
||||
let raw = itemBody rawItem
|
||||
case Y.decodeEither' (BS.pack raw) of
|
||||
-- encodeUtf8, not Char8.pack: Char8 truncates each Char to 8 bits,
|
||||
-- silently corrupting any codepoint above 0x7F (same hazard Now.hs
|
||||
-- documents — em-dash 0x2014 would become control char 0x14).
|
||||
case Y.decodeEither' (TE.encodeUtf8 (T.pack raw)) of
|
||||
Left err -> fail ("commonplace.yaml: " ++ show err)
|
||||
Right entries -> return entries
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ module Contexts
|
|||
, recentFirstByDisplay
|
||||
, Revision (..)
|
||||
, getRevisions
|
||||
, isProvedConfidence
|
||||
) where
|
||||
|
||||
import Data.Aeson (Value (..))
|
||||
|
|
@ -86,7 +87,12 @@ affiliationField = listFieldWith "affiliation-links" ctx $ \item -> do
|
|||
let entries = case lookupStringList "affiliation" meta of
|
||||
Just xs -> xs
|
||||
Nothing -> maybe [] (:[]) (lookupString "affiliation" meta)
|
||||
return $ map (Item (fromFilePath "") . parseEntry) entries
|
||||
-- noResult, not an empty list: Hakyll's $if$ treats an empty
|
||||
-- ListField as truthy, so returning [] would render the wrapper
|
||||
-- markup (an empty .meta-affiliation row) on every page.
|
||||
if null entries
|
||||
then noResult "no affiliation"
|
||||
else return $ map (Item (fromFilePath "") . parseEntry) entries
|
||||
where
|
||||
ctx = field "affiliation-name" (return . fst . itemBody)
|
||||
<> field "affiliation-url" (\i -> let u = snd (itemBody i)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import Commonplace (commonplaceCtx)
|
|||
import Now (nowCtx)
|
||||
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
|
||||
contentKindField, recentFirstByDisplay,
|
||||
tagLinksFieldExcludingTopSegment)
|
||||
tagLinksFieldExcludingTopSegment, isProvedConfidence)
|
||||
import qualified Patterns as P
|
||||
import Photography (photographyRules)
|
||||
import Tags (buildAllTags, applyTagRules, sidecarIdentifier,
|
||||
|
|
@ -1011,8 +1011,12 @@ epistemicEntry item = do
|
|||
, grab "stability" meta
|
||||
]
|
||||
obj = Map.fromList fields
|
||||
-- Compute overall-score the same way Contexts.overallScoreField does.
|
||||
obj' = case ( readMaybe =<< lookupString "confidence" meta :: Maybe Int
|
||||
-- Compute overall-score the same way Contexts.overallScoreField
|
||||
-- does, including the "proved"/"proven" sentinel -> 100.
|
||||
confRaw = lookupString "confidence" meta
|
||||
confInt | isProvedConfidence confRaw = Just 100
|
||||
| otherwise = readMaybe =<< confRaw :: Maybe Int
|
||||
obj' = case ( confInt
|
||||
, readMaybe =<< lookupString "evidence" meta :: Maybe Int
|
||||
) of
|
||||
(Just conf, Just ev) ->
|
||||
|
|
|
|||
|
|
@ -181,8 +181,11 @@ parseDay :: String -> Maybe Day
|
|||
parseDay = parseTimeM True defaultTimeLocale "%Y-%m-%d"
|
||||
|
||||
-- | First Monday on or before 'day' (start of its ISO week).
|
||||
-- 'fromEnum' on 'DayOfWeek' is ISO-numbered (Monday=1 .. Sunday=7),
|
||||
-- so Monday must subtract 0 days, Sunday 6.
|
||||
weekStart :: Day -> Day
|
||||
weekStart day = addDays (fromIntegral (negate (fromEnum (dayOfWeek day)))) day
|
||||
weekStart day =
|
||||
addDays (fromIntegral (negate (fromEnum (dayOfWeek day) - 1))) day
|
||||
|
||||
-- | Intensity class for the heatmap (hm0 … hm4).
|
||||
heatClass :: Int -> String
|
||||
|
|
@ -297,7 +300,7 @@ renderHeatmap wordsByDay today =
|
|||
nDays = diffDays today startDay + 1
|
||||
allDays = [addDays i startDay | i <- [0 .. nDays - 1]]
|
||||
weekOf d = fromIntegral (diffDays d startDay `div` 7) :: Int
|
||||
dowOf d = fromEnum (dayOfWeek d) -- Mon=0..Sun=6
|
||||
dowOf d = fromEnum (dayOfWeek d) - 1 -- ISO 1..7 -> Mon=0..Sun=6
|
||||
svgW = (nWeeks - 1) * step + cellSz
|
||||
svgH = 6 * step + cellSz + hdrH
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue