fix(scripts): green check-phase10 hygiene after Phase 17 and run it from check-phase17 (T-10-24, T-10-25)
- line-exact, path-bound T-17-10 raw-HTML exceptions for the RichEditorField read-only display and the inert template parse in lossy.ts - dist-origin allow-list adds the never-requested literals https://prosemirror.net and https://admin.invalid - self-test plants: the display line in another file, a template parse elsewhere, a foreign origin in the dist JS - --spa also runs the Vitest browser suite, fail-closed - check-phase17: browser-suite required names in the admin stage, Phase 10 hygiene in the docs stage, asserted by its self-test
This commit is contained in:
@@ -4,6 +4,13 @@
|
||||
# named test that did not run, OpenAPI or dist drift, a hygiene violation or
|
||||
# an evidence gap. --self-test proves each detector fails closed.
|
||||
#
|
||||
# Hygiene exceptions are line-exact and bound to their file: the MarkdownField
|
||||
# preview (server-rendered), and since Phase 17 (T-17-10) the RichEditorField
|
||||
# read-only view (DOMPurify displayHtml, T-10-16) and the inert template parse
|
||||
# in richeditor/lossy.ts. The dist-origin rule allows only the literals that
|
||||
# are never requested (namespace URIs, docs links in error text, the
|
||||
# reserved-TLD parse base of isAllowedLink).
|
||||
#
|
||||
# Allow-list: none. The fonoteka.go parity failures TestMigrateSeedsCanonicalGenres
|
||||
# and TestSchemaMatchesPHPSnapshot, accepted until then, pass since fonoteka.go
|
||||
# 21c0f12 (fix(09): update parity expectations for the backend admin schema),
|
||||
@@ -224,9 +231,10 @@ run_self_test() {
|
||||
done
|
||||
|
||||
# The hygiene stage passes on a scratch copy of the tree and fails once a
|
||||
# raw-HTML directive, a direct fetch, an XMLHttpRequest outside the files.ts
|
||||
# upload, an app name, a browser-storage call or an extra admin/src/api file
|
||||
# is planted in it.
|
||||
# raw-HTML directive (also the RichEditorField display line or the lossy.ts
|
||||
# template parse moved to another file), a direct fetch, an XMLHttpRequest
|
||||
# outside the files.ts upload, an app name, a browser-storage call, an extra
|
||||
# admin/src/api file or another origin in the dist JS is planted in it.
|
||||
local scratch
|
||||
scratch="$(mktemp -d)"
|
||||
trap 'rm -rf "$scratch"' RETURN
|
||||
@@ -242,8 +250,9 @@ run_self_test() {
|
||||
# Each plant is imported by a scratch test, so only its own rule can fire;
|
||||
# the refusal must name that rule.
|
||||
local plant want out
|
||||
for plant in vhtml fetch appname storage xhr apifile; do
|
||||
rm -rf "$scratch/admin/src/__plant" "$scratch/admin/tests/__plant.test.ts" "$scratch/admin/src/api/__plant.ts"
|
||||
for plant in vhtml vhtml-elsewhere template-parse fetch appname storage xhr apifile origin; do
|
||||
rm -rf "$scratch/admin/src/__plant" "$scratch/admin/tests/__plant.test.ts" "$scratch/admin/src/api/__plant.ts" \
|
||||
"$scratch/modules/boardwalk/dist/assets/__plant.js"
|
||||
mkdir -p "$scratch/admin/src/__plant"
|
||||
case "$plant" in
|
||||
vhtml)
|
||||
@@ -251,6 +260,20 @@ run_self_test() {
|
||||
printf "import Plant from '../src/__plant/Plant.vue'\n" >"$scratch/admin/tests/__plant.test.ts"
|
||||
want="raw-HTML directive"
|
||||
;;
|
||||
vhtml-elsewhere)
|
||||
# The RichEditorField exception is bound to its path.
|
||||
printf '<template>\n%s\n</template>\n' " <div v-if=\"display !== ''\" class=\"summer-markdown\" v-html=\"display\" />" >"$scratch/admin/src/__plant/Plant.vue"
|
||||
printf "import Plant from '../src/__plant/Plant.vue'\n" >"$scratch/admin/tests/__plant.test.ts"
|
||||
want="raw-HTML directive"
|
||||
;;
|
||||
template-parse)
|
||||
printf "export function parse(html: string) {\n const template = document.createElement('template')\n template.innerHTML = html\n return template.content\n}\n" >"$scratch/admin/src/__plant/plant.ts"
|
||||
want="raw-HTML directive"
|
||||
;;
|
||||
origin)
|
||||
printf 'const u="https://evil.example/x";export{u};\n' >"$scratch/modules/boardwalk/dist/assets/__plant.js"
|
||||
want="JS names another origin"
|
||||
;;
|
||||
fetch)
|
||||
printf 'export const load = () => fetch("/x")\n' >"$scratch/admin/src/__plant/plant.ts"
|
||||
want="direct fetch"
|
||||
@@ -330,6 +353,19 @@ run_spa() {
|
||||
exit 1
|
||||
fi
|
||||
grep -E 'Test Files|Tests ' "$log" || true
|
||||
# The richeditor display needs a real engine (T-10-16): Vitest browser mode
|
||||
# in Playwright Chromium, refused the same fail-closed way.
|
||||
set +e
|
||||
npm --prefix "$ROOT/admin" run test:browser >"$log" 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ "$rc" -ne 0 ]] || grep -qE 'No test files found|Unhandled|FAIL | skipped| todo' "$log"; then
|
||||
tail -n 60 "$log" >&2
|
||||
rm -f "$log"
|
||||
echo "refuse: admin Vitest browser run failed (exit $rc)" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -E 'Test Files|Tests ' "$log" || true
|
||||
rm -f "$log"
|
||||
echo "phase10 spa passed"
|
||||
}
|
||||
@@ -364,9 +400,16 @@ hygiene_checks() {
|
||||
admin/src admin/tests admin/openapi modules/boardwalk modules/cabana modules/phrasebook 2>/dev/null || true)"
|
||||
[[ -z "$hits" ]] || fail "application names in the framework: $hits"
|
||||
# Plugin and server strings are rendered as text only.
|
||||
# The markdown preview binds only the HTML POST /markdown/preview answers (cabana.RenderMarkdown); every other raw-HTML sink is still refused.
|
||||
# The markdown preview binds only the HTML POST /markdown/preview answers (cabana.RenderMarkdown).
|
||||
# T-17-10: the RichEditorField read-only view binds only displayHtml output (DOMPurify, T-10-16), never the stored value.
|
||||
# T-17-10: lossy.ts parses into a template, whose contents are inert and are read for tag names only, never bound.
|
||||
# Each exception is the exact line in its own file; every other raw-HTML sink is still refused.
|
||||
local richeditor_display
|
||||
richeditor_display="^admin/src/components/form/fields/RichEditorField\\.vue:[0-9]+:[[:space:]]*<div v-if=\"display !== ''\" class=\"summer-markdown\" v-html=\"display\" />[[:space:]]*\$"
|
||||
hits="$(cd "$tree" && grep -rnE 'v-html|innerHTML|outerHTML|insertAdjacentHTML' admin/src 2>/dev/null |
|
||||
grep -vE '^admin/src/components/form/fields/MarkdownField\.vue:[0-9]+:[[:space:]]*v-html="sanitizedHtml"[[:space:]]*$' || true)"
|
||||
grep -vE -e '^admin/src/components/form/fields/MarkdownField\.vue:[0-9]+:[[:space:]]*v-html="sanitizedHtml"[[:space:]]*$' \
|
||||
-e "$richeditor_display" \
|
||||
-e '^admin/src/components/form/richeditor/lossy\.ts:[0-9]+:[[:space:]]*template\.innerHTML = html[[:space:]]*$' || true)"
|
||||
[[ -z "$hits" ]] || fail "raw-HTML directive in admin/src: $hits"
|
||||
# All HTTP goes through the typed openapi-fetch client.
|
||||
# Uploads in admin/src/api/files.ts use XMLHttpRequest because only XHR reports upload progress (12.2-04); that XHR sends X-Requested-With (T-12.2-32) and refreshes through client.ts; every other XHR, fetch or axios call is still refused.
|
||||
@@ -387,11 +430,13 @@ hygiene_checks() {
|
||||
hits="$(cd "$tree" && grep -rnE 'localStorage|sessionStorage|indexedDB|document\.cookie' admin/src 2>/dev/null |
|
||||
grep -v -e '^admin/src/state/useSidebar.ts:' -e '^admin/src/state/listState.ts:' || true)"
|
||||
[[ -z "$hits" ]] || fail "browser storage outside admin/src/state/useSidebar.ts and admin/src/state/listState.ts: $hits"
|
||||
# The embedded build loads nothing from another origin.
|
||||
# The embedded build loads nothing from another origin. Allowed literals are
|
||||
# never requested: https://prosemirror.net is ProseMirror error text and
|
||||
# https://admin.invalid the reserved-TLD parse base in isAllowedLink.
|
||||
hits="$(cd "$tree" && grep -noE '(src|href)="(https?:)?//[^"]*"|url\((["'"'"']?)(https?:)?//' modules/boardwalk/dist/index.html modules/boardwalk/dist/assets/*.css 2>/dev/null || true)"
|
||||
[[ -z "$hits" ]] || fail "modules/boardwalk/dist references another origin: $hits"
|
||||
hits="$(cd "$tree" && grep -ohE 'https?://[A-Za-z0-9.-]+' modules/boardwalk/dist/assets/*.js 2>/dev/null | sort -u |
|
||||
grep -vxE 'http://www\.w3\.org|https://vuejs\.org|http://local' || true)"
|
||||
grep -vxE 'http://www\.w3\.org|https://vuejs\.org|http://local|https://prosemirror\.net|https://admin\.invalid' || true)"
|
||||
[[ -z "$hits" ]] || fail "modules/boardwalk/dist JS names another origin: $hits"
|
||||
# Icons: named imports only, and never the deprecated lucide package.
|
||||
hits="$(cd "$tree" && grep -rnE "import \* as [A-Za-z_]+ from '@lucide/vue'|lucide-vue-next" admin/src admin/package.json 2>/dev/null || true)"
|
||||
|
||||
Reference in New Issue
Block a user