test: restore Astro verification contracts

Reinstate all 42 legacy facts as output or authoritative-source contracts, retain output snapshots, and set the 84-assertion floor. Extend the audit count without changing site content or components.
This commit is contained in:
Marcos Paulo
2026-09-05 23:41:23 +00:00
parent c7d06a120d
commit 8c7ca2aded
4 changed files with 246 additions and 2 deletions
+4 -1
View File
@@ -52,7 +52,10 @@ pnpm run verify
# Compare against origin/main and refuse a silent reduction. # Compare against origin/main and refuse a silent reduction.
step "assertion coverage" step "assertion coverage"
current=$(grep -c 'throw new Error' scripts/verify.mjs) current=$(grep -c 'throw new Error' scripts/verify.mjs)
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo "$current") # Task 19 restored the 42 legacy facts and added ten output snapshots: 84 is
# now the floor, in addition to whatever origin/main currently requires.
baseline=$(git show origin/main:scripts/verify.mjs 2>/dev/null | grep -c 'throw new Error' || echo 0)
if [ "$baseline" -lt 84 ]; then baseline=84; fi
if [ "$current" -lt "$baseline" ]; then if [ "$current" -lt "$baseline" ]; then
echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2 echo "gate: verify.mjs coverage fell from $baseline to $current assertions." >&2
echo " Only verification-engineer may reduce it, with a reason per removal." >&2 echo " Only verification-engineer may reduce it, with a reason per removal." >&2
@@ -164,3 +164,54 @@ review-desk interaction modes.
Start from `rejected/19-attempt-1` rather than from scratch — the `dist/` Start from `rejected/19-attempt-1` rather than from scratch — the `dist/`
re-point and the Portuguese coverage check are worth keeping. re-point and the Portuguese coverage check are worth keeping.
## Attempt 2 contract mapping
Baseline: 42 original assertions. `scripts/verify.mjs` now has 84 assertion
sites: the 42 output/snapshot assertions from attempt 1 plus the 42 contracts
below. No assertion was removed. `gate.sh` enforces 84 as the new floor.
| Original assertion message | Current assertion that pins the same fact | Removal reason |
| --------------------------------------------------------------- | ------------------------------------------------------------ | -------------- |
| `missing reference` | `missing reference contract` | — |
| `missing content` | `missing full-guide content contract` | — |
| `missing interaction` | `missing full-guide interaction contract` | — |
| `missing starter content` | `missing starter content contract` | — |
| `missing starter behavior` | `missing starter behavior contract` | — |
| `missing additional source` | `missing additional-reading source contract` | — |
| `fewer than five additional readings` | `additional-reading coverage fell below five entries` | — |
| `missing pinned skill source` | `missing pinned skill-source contract` | — |
| `missing model source` | `missing model-source contract` | — |
| `external runtime dependency found` | `full-guide has an external runtime dependency` | — |
| `missing rules content` | `missing rules content contract` | — |
| `missing rules interaction` | `missing rules interaction contract` | — |
| `missing rules control` | `missing rules-control contract` | — |
| `main presentation does not link to rules page` | `full-guide no longer links to rules` | — |
| `main presentation does not link to skills review page` | `full-guide no longer links to skills review` | — |
| `main presentation missing chapter route` | `full-guide is missing a chapter route` | — |
| `rules page has an external runtime dependency` | `rules page has an external runtime dependency` | — |
| `missing rules responsive contract` | `missing rules responsive contract` | — |
| `missing review page content` | `missing review page-content contract` | — |
| `missing review interaction` | `missing review interaction contract` | — |
| `missing review file manifest` | `missing review file-manifest contract` | — |
| `review catalog does not cover all submissions` | `review catalog no longer covers all submissions` | — |
| `review catalog does not record secret safety handling` | `review catalog lost its secret-safety contract` | — |
| `chapter page missing shared navigation` | `chapter page missing shared navigation` | — |
| `missing chapter responsive contract` | `missing chapter responsive contract` | — |
| `landing page missing` | `landing page missing a route-map destination` | — |
| `landing page should remain a fast, static route map` | `landing page is no longer a static route map` | — |
| `skills anatomy missing` | `skills anatomy missing package contract` | — |
| `skills anatomy interaction missing` | `skills anatomy lost its interaction module` | — |
| `skills anatomy responsive contract missing` | `skills anatomy lost responsive contract` | — |
| `operational submission privacy leak` | `operational submission privacy leak` | — |
| `operational submission identity remains in review desk source` | `operational submission identity remains in review output` | — |
| `operational submission redaction contract missing` | `operational submission redaction contract missing` | — |
| `review file-mode contract missing` | `review file-mode contract missing` | — |
| `file selection still resets improved mode` | `review file selection no longer preserves preview mode` | — |
| `review change-lens contract missing` | `review change-lens contract missing` | — |
| `review change-lens CSS missing` | `review change-lens CSS contract missing` | — |
| `review markdown preview contract missing` | `review markdown-preview contract missing` | — |
| `review vote widget wiring missing` | `review vote-widget island is not wired to a hashed asset` | — |
| `review vote widget contract missing` | `review vote-widget contract missing` | — |
| `review vote widget CSS missing` | `review vote-widget CSS contract missing` | — |
| `vote-service missing IP-based one-vote-per-source contract` | `vote-service missing IP-based one-vote-per-source contract` | — |
+1
View File
@@ -29,6 +29,7 @@ const builtValues = {
colors: new Set(builtCss.match(/#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\([^)]*\)/g) || []), colors: new Set(builtCss.match(/#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\([^)]*\)/g) || []),
sizes: new Set(builtCss.match(/\b\d+(?:\.\d+)?(?:px|rem|em|vw|vh)\b/g) || []), sizes: new Set(builtCss.match(/\b\d+(?:\.\d+)?(?:px|rem|em|vw|vh)\b/g) || []),
}; };
if (!builtCss) throw new Error('Astro build emitted no CSS assets to audit');
// Captured from origin/main after its Astro build. This catches a value that is // Captured from origin/main after its Astro build. This catches a value that is
// silently changed to a nearby token: source token checks cannot see that drift. // silently changed to a nearby token: source token checks cannot see that drift.
const baselineValues = JSON.parse(read('.agents/snapshots/built-css-values.json')); const baselineValues = JSON.parse(read('.agents/snapshots/built-css-values.json'));
+190 -1
View File
@@ -1,4 +1,4 @@
import { readFileSync } from 'node:fs'; import { readFileSync, readdirSync } from 'node:fs';
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8'); const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
const built = (path) => read(`dist/${path}`); const built = (path) => read(`dist/${path}`);
@@ -33,6 +33,23 @@ const html = {
const snapshot = (path) => normalize(read(`.agents/snapshots/${path}`)); const snapshot = (path) => normalize(read(`.agents/snapshots/${path}`));
const reviewAssetSrc = html.review.match(/src="([^"]+\.[a-zA-Z0-9_-]{8,}\.js)"/)?.[1]; const reviewAssetSrc = html.review.match(/src="([^"]+\.[a-zA-Z0-9_-]{8,}\.js)"/)?.[1];
const reviewAsset = reviewAssetSrc ? read(`dist/_astro/${reviewAssetSrc.split('/').at(-1)}`) : ''; const reviewAsset = reviewAssetSrc ? read(`dist/_astro/${reviewAssetSrc.split('/').at(-1)}`) : '';
const builtCss = readdirSync(new URL('../dist/_astro/', import.meta.url))
.filter((file) => file.endsWith('.css'))
.map((file) => read(`dist/_astro/${file}`))
.join('\n');
const source = {
references: read('docs/references/README.md'),
additionalReading: read('docs/references/additional-reading.md'),
skillSources: read('src/content/skillSources/ponytail.json'),
providers: [
read('src/content/providers/openai.json'),
read('src/content/providers/claude.json'),
read('src/content/providers/gemini.json'),
].join('\n'),
starter: read('hands-on/starter/app.js'),
voteService: read('vote-service/main.go'),
ndoReview: read('src/content/reviews/ndo-repro.md'),
};
// Rendered prose catches a deleted paragraph that string-token checks miss. // Rendered prose catches a deleted paragraph that string-token checks miss.
if (rendered(html.landing) !== snapshot('index.txt')) if (rendered(html.landing) !== snapshot('index.txt'))
@@ -260,4 +277,176 @@ if (
) )
throw new Error('operational review content has a privacy leak'); throw new Error('operational review content has a privacy leak');
// These retain the pre-Astro contract one fact at a time. Visible facts read
// from dist; source-only contracts remain at their authoritative boundary.
if (
![
'https://code.claude.com/docs/en/sub-agents',
'https://code.claude.com/docs/en/skills',
'https://code.claude.com/docs/en/worktrees',
'https://git-scm.com/docs/git-worktree.html',
'https://developers.openai.com/codex/skills',
].every((url) => source.references.includes(url))
)
throw new Error('missing reference contract');
if (!has(html.guide, ['data-phase="plan"', 'data-copy-target="prompt-basic"']))
throw new Error('missing full-guide content contract');
if (!has(html.guide, ['data-route="plan"', 'data-skill-file="skill"', '<script type="module">']))
throw new Error('missing full-guide interaction contract');
if (!has(html.starter, ['id="task-list"', 'id="task-count"']))
throw new Error('missing starter content contract');
if (!['const tasks', 'renderTasks()'].every((token) => source.starter.includes(token)))
throw new Error('missing starter behavior contract');
if (
![
'medium.com',
'anthropic.com/engineering',
'openai.com/business',
'git-scm.com/docs/git-worktree',
].every((url) => source.additionalReading.includes(url))
)
throw new Error('missing additional-reading source contract');
if ((source.additionalReading.match(/^### \d+\./gm) || []).length < 5)
throw new Error('additional-reading coverage fell below five entries');
if (
![
'e7b42dc2d384a702240dea4d52a7bf5530b821b6',
'6654f6b60cd9d5be8b54c6fafe44346dabeb3b76',
'53048666b05b4799081517d00e09e0a2dd688678',
].every((sha) => `${source.skillSources}\n${html.guide}`.includes(sha))
)
throw new Error('missing pinned skill-source contract');
if (
![
'developers.openai.com/api/docs/guides/latest-model',
'docs.anthropic.com/en/docs/claude-code/model-config',
'ai.google.dev/gemini-api/docs/thinking',
].every((url) => `${source.providers}\n${html.guide}`.includes(url))
)
throw new Error('missing model-source contract');
if (/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(html.guide))
throw new Error('full-guide has an external runtime dependency');
if (!has(html.rules, ['id="pipeline"', 'id="skills"', 'id="examples"']))
throw new Error('missing rules content contract');
if (!has(html.rules, ['data-stage="context"', 'data-copy-prompt', '<script>']))
throw new Error('missing rules interaction contract');
if (!has(html.rules, ['data-lang="en"', 'data-lang="pt"', 'aria-live="polite"']))
throw new Error('missing rules-control contract');
if (!html.guide.includes('href="/ai-for-dummies/rules/"'))
throw new Error('full-guide no longer links to rules');
if (!html.guide.includes('href="/ai-for-dummies/skills-review/"'))
throw new Error('full-guide no longer links to skills review');
if (!['summary/', 'models/', 'agents/', 'skills/'].every((route) => html.guide.includes(route)))
throw new Error('full-guide is missing a chapter route');
if (/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i.test(html.rules))
throw new Error('rules page has an external runtime dependency');
if (
!['@media(max-width:800px)', '@media(min-width:2200px)', 'prefers-reduced-motion'].every(
(token) => builtCss.includes(token),
)
)
throw new Error('missing rules responsive contract');
if (!has(html.review, ['id="catalog"', 'id="skill-filter"', 'id="skill-list"', 'id="detail"']))
throw new Error('missing review page-content contract');
if (
!['data-render', 'preview-markdown', 'data-file', 'URLSearchParams', 'navigator.clipboard'].every(
(token) => reviewAsset.includes(token),
)
)
throw new Error('missing review interaction contract');
if (
!['reference.md', 'ndo-repro', 'semantic-diff-review'].every((token) =>
html.review.includes(token),
)
)
throw new Error('missing review file-manifest contract');
if ((html.review.match(/data-skill-id=/g) || []).length !== 24)
throw new Error('review catalog no longer covers all submissions');
if (!source.ndoReview.includes('safety-redacted') || !reviewAsset.includes('[REDACTED]'))
throw new Error('review catalog lost its secret-safety contract');
if (
![html.summary, html.models, html.agents, html.skills].every((page) => page.includes('ROUTE MAP'))
)
throw new Error('chapter page missing shared navigation');
if (
!['@media(max-width:800px)', '@media(max-width:520px)', '--ink'].every((token) =>
builtCss.includes(token),
)
)
throw new Error('missing chapter responsive contract');
if (!has(html.landing, ['The short route', 'full-guide/', 'skills-review/']))
throw new Error('landing page missing a route-map destination');
if (/<script\b/i.test(html.landing))
throw new Error('landing page is no longer a static route map');
if (
!has(html.skills, [
'data-skill-file="skill"',
'data-skill-file="references"',
'id="package-preview"',
])
)
throw new Error('skills anatomy missing package contract');
if (!html.skills.includes('<script>'))
throw new Error('skills anatomy lost its interaction module');
if (
!['overflow-wrap:anywhere', '@media(max-width:800px)', 'prefers-reduced-motion'].every((token) =>
builtCss.includes(token),
)
)
throw new Error('skills anatomy lost responsive contract');
if (
/Pedro\s+Aranha|pedro\.aranha|git\.netcracker\.com|artifactorycn|managed\.netcracker\.cloud/i.test(
source.ndoReview,
)
)
throw new Error('operational submission privacy leak');
if (html.review.includes('Pedro%20Aranha') || html.review.includes('pedro.aranha'))
throw new Error('operational submission identity remains in review output');
if (
!html.review.includes('Anonymous%20Operational%20Submission') ||
!reviewAsset.includes('[REDACTED LOCAL USER]')
)
throw new Error('operational submission redaction contract missing');
if (!['dataset.preview', 'dataset.file'].every((token) => reviewAsset.includes(token)))
throw new Error('review file-mode contract missing');
if (!reviewAsset.includes('preview-markdown'))
throw new Error('review file selection no longer preserves preview mode');
if (!['data-lens', 'CHANGE LENS', 'What changed'].every((token) => reviewAsset.includes(token)))
throw new Error('review change-lens contract missing');
if (
!['.change-lens', '.change-rows', '.skill-diff', '@media(max-width:620px)'].every((token) =>
builtCss.includes(token),
)
)
throw new Error('review change-lens CSS contract missing');
if (
!['.markdown-preview', 'max-height:540px', '.markdown-table-wrap', '.markdown-toc'].every(
(token) => builtCss.includes(token),
)
)
throw new Error('review markdown-preview contract missing');
if (
!html.review.includes(
'src="/ai-for-dummies/_astro/skills-review.astro_astro_type_script_index_0_lang.',
)
)
throw new Error('review vote-widget island is not wired to a hashed asset');
if (
!['id="vote-widget"', 'X-Voter-Id', '/api/votes', 'Voting is offline'].every((token) =>
`${html.review}\n${reviewAsset}`.includes(token),
)
)
throw new Error('review vote-widget contract missing');
if (
!['.vote-widget', '.vote-buttons', '[aria-pressed="true"]'].every((token) =>
builtCss.includes(token),
)
)
throw new Error('review vote-widget CSS contract missing');
if (
!source.voteService.includes('X-Forwarded-For') ||
!source.voteService.includes('one active vote per skill')
)
throw new Error('vote-service missing IP-based one-vote-per-source contract');
console.log('built output verification passed'); console.log('built output verification passed');