diff --git a/docs/dream-cycle/LEDGER.md b/docs/dream-cycle/LEDGER.md index ea369a4..28704f3 100644 --- a/docs/dream-cycle/LEDGER.md +++ b/docs/dream-cycle/LEDGER.md @@ -32,3 +32,5 @@ Schema note (2026-09-14, unmerged): FR6.6 of PRD-augmentation-conditions adds `R | 2026-09-30 | seo-and-meta | Given the shipped page at `d4e4429` declares an Organization but never declares | NONE | https://github.com/DreamLab-AI/VisionFlow/pull/4 | yes | ACCEPT | | 65a9d02cf480 | | | | | 2026-10-01 | build-pipeline | Given `website/static/robots.txt` reaches `dist/` only through `build.sh`'s whol | NONE | https://github.com/DreamLab-AI/VisionFlow/pull/11 | yes | ACCEPT | | 7f88b6695afe | | | | | 2026-10-02 | seo-and-meta | Given the shipped page at `2480a8f` declares its brand only in ``/nav cop | NONE | https://github.com/DreamLab-AI/VisionFlow/pull/12 | yes | ACCEPT | | 369fbc125f52 | | | | +| 2026-10-03 | estate-health | Given canon declares seven sidestr crates on crates.io while `scripts/estate-hea | NONE | https://github.com/DreamLab-AI/VisionFlow/pull/13 | yes | ACCEPT | | 9c56f9ff2d42 | | | | +| 2026-10-04 | content-integrity | Given the shipped page's `href="#…"` anchors all resolve to `id="…"` targets in | NONE | https://github.com/DreamLab-AI/VisionFlow/pull/14 | yes | ACCEPT | | 617ce2f95547 | | | | diff --git a/scripts/dream-link-check.sh b/scripts/dream-link-check.sh index 9139cdc..8240414 100755 --- a/scripts/dream-link-check.sh +++ b/scripts/dream-link-check.sh @@ -3,7 +3,7 @@ # the built static site. Emits a surface-dependent count; non-zero missing # refs prints LINK-INTEGRITY-FAIL. Used as a dream-cycle evaluator entrypoint. # -# Two ref families are resolved: +# Three ref families are resolved: # 1. href/src attributes — ordinary markup links and asset references. # 2. meta[content] media URLs (og:image, twitter:image, …). These were a # shared blind spot: dream-meta-tags-scan.sh COUNTS the tags but never @@ -11,6 +11,12 @@ # og:image pointing at a file absent from dist/ passed both gates. The # social card is a required asset in website/assets.manifest.json, and # this makes that requirement observable from the link surface too. +# 3. in-page hash anchors. href="#id" must resolve to an id="id" target in +# the same document. Family 1's pattern excludes every ref containing +# '#', so pure-hash anchors (the entire nav) were invisible to this gate +# (2026-09-29 ledger). The 2026-10-04 content-integrity night verified a +# 0-dangling baseline, and the operator approved (2026-10-02) promoting +# dangling > 0 to a failing condition. # # Social URLs are absolute on the canonical domain (they must be, for the # crawlers), so "site-relative" here includes same-origin absolute URLs: the @@ -38,7 +44,7 @@ resolve_ref() { esac } -miss=0; checked=0 +miss=0; checked=0; anchors=0; dangling=0 while IFS= read -r f; do d=$(dirname "$f") @@ -58,6 +64,17 @@ while IFS= read -r f; do checked=$((checked+1)) [ -e "$d/$t" ] || [ -e "./$t" ] || { echo "MISSING: $f -> $ref"; miss=$((miss+1)); } done + + # 3. in-page hash anchors — must resolve to an id= target in the same + # document. Family 1 skips refs containing '#', so a nav or card link + # to a missing section id passed this gate unnoticed. + for ref in $(grep -oE '[[:space:]]href="#[^"]+"' "$f" 2>/dev/null | sed -E 's/.*href="#([^"]*)"/\1/'); do + [ -n "$ref" ] || continue + anchors=$((anchors+1)) + grep -qE "[[:space:]]id=\"$ref\"" "$f" 2>/dev/null \ + || { echo "DANGLING-ANCHOR: $f -> #$ref"; dangling=$((dangling+1)); } + done done < <(find . -name '*.html') echo "internal-refs-checked: $checked missing: $miss" -[ "$miss" -eq 0 ] && echo LINK-INTEGRITY-OK || echo LINK-INTEGRITY-FAIL +echo "anchors-checked: $anchors dangling: $dangling" +[ "$miss" -eq 0 ] && [ "$dangling" -eq 0 ] && echo LINK-INTEGRITY-OK || echo LINK-INTEGRITY-FAIL