Skip to content

feat: contract rule IDs, a docs checker and a kb-docs-add audit mode; fix stale pierced CSS - #100

Merged
oto-macenauer-absa merged 3 commits into
masterfrom
fix/css-isolation-web-fragment
Sep 23, 2026
Merged

oto-macenauer-absa merged 3 commits into
masterfrom
fix/css-isolation-web-fragment

Conversation

@oto-macenauer-absa

Copy link
Copy Markdown
Collaborator

Follow-up to #99. There are three commits, and each one can be reviewed on its own.

1. fix: absolute CSS URLs, and no stale portal copies in a pierced fragment

Measured with the embedded harness in all three modes: pierced, bound and unbound.

  • The first app's CSS followed the visitor everywhere (pierced only). When reframed portals a server-rendered fragment, it copies every linked sub-app stylesheet into an adopted constructed sheet. It drops the copy only once it sees the sheet fetched again. On the moveBefore() path the <link> is never re-fetched, so the copy stays for the life of the fragment. embedded-transitions.js now drops the knowledge base's own copies before the first swap. It recognises them by the layer-order statement that every built sub-app stylesheet opens with.
  • Relative url() in sub-app CSS returned 404s. The copy resolves URLs against the host document, and the host router strips the trailing slash. So showcase.css requested /knowledge-base/docs/assets/images/hero.png. copyAssets() now makes every url() and @import absolute, resolved against the stylesheet's own URL. It uses rewriteCssUrls(), shared with the inline-style rewrite.
  • Considered and rejected: serving scripts and styles from a separate path prefix. The gateway routes requests by sec-fetch-dest, not by path. A build retargeted to /__wf/knowledge-base/ reproduced both failures unchanged.

2. feat(contract): rule IDs and a checker for published docs

  • contract/RULES.md lists every rule a published docs site is checked against, by ID (KB-HTML-003 …), with its severity, the reason and the fix. actions/lib/rules.js is the code side, and the self-test fails if the two disagree.
  • actions/lib/check.js replaces verify-html.js. It parses HTML with htmlparser2 and CSS with postcss. The existing checks keep their severity and wording, now prefixed with the rule ID.
  • New rules, all warnings, so no repo pinned to @v1 starts failing:
    • inline on* handlers and javascript: URLs, which never run under script-src 'self';
    • a site-level <header>;
    • links to non-HTML files in the app;
    • !important;
    • dark mode;
    • scripts, stylesheets or fonts from another origin;
    • fetches of page-relative URLs.
  • actions/lib/check-cli.js runs the same checks without publishing, for a docs repo's PR CI or an agent. It supports --json and --strict.

3. feat(skills): audit mode for kb-docs-add

An already-onboarded repo now takes the audit path in references/audit.md:

  1. Check the workflow setup.
  2. Build what the workflow publishes.
  3. Run the checker from a scratch clone.
  4. Fix each finding at its source, by rule ID.
  5. Report what the checker cannot see.

Testing

  • npm test: 372 of 372 passed on the tree for commits 1 and 2. Commit 3 only touches the skill, its fixtures and CLAUDE.md, and tests/skill.spec.js passes on it (30 of 30).
  • The new tests fail without the fix in commit 1. In the pierced project: build-integrity (relative url()) and css-isolation ("starting on an app page").
  • npm run selftest in actions/ passes. It includes the new check.selftest.js: one case per rule, a clean site that must produce no findings, the CLI, and the RULES.md ↔ rules.js sync check.
  • The checker on real artifacts:
    • the vendored docs-example fixture: 0 errors, 28 genuine warnings;
    • the single-page bundle: no findings.
  • Eval 4: tests/fixtures/kb-docs-add/onboarded-mkdocs is an onboarded mkdocs repo with problems planted in it.
    • Built with mkdocs 1.6.1, it gives 3 errors and 18 warnings. The fixes the skill prescribes bring it to no findings.
    • Two runs of an agent following only the skill met 12 of 13 and then 14 of 14 assertions. The miss in the first run was an assertion that was too strict.

Notes for reviewers

  • Nothing reaches docs repos until a new v1.x tag moves v1. Until then, audit.md tells agents to clone without --branch.
  • htmlparser2 and postcss become direct dependencies of actions/, at the versions its lockfile already carried.
  • The knowledge base build does not run the checker yet. It doesn't install the actions/ dependencies, so that needs a decision on how.

Refs #67. The checker now reports inline handlers (KB-HTML-005), but the build still does not strip them.

🤖 Generated with Claude Code

oto-macenauer-absa and others added 3 commits September 23, 2026 10:58
Embedded with server-side piercing, reframed copies every linked sub-app
stylesheet into an adopted constructed stylesheet when it portals the
fragment, and only drops the copy once the sheet is fetched again. On the
moveBefore() path it never is, so the first app's CSS stayed applied to
the catalog and to every other app for the life of the fragment. The copy
also resolves url() against the host document, whose router strips the
trailing slash, so a relative url() in a sub-app stylesheet 404'd
(/knowledge-base/docs/... instead of /knowledge-base/user-guide/docs/...).

- copyAssets() now makes every url() and @import in copied sub-app CSS
  absolute, resolving relative ones against the stylesheet's own URL
  (rewriteCssUrls(), shared with the inline-style rewrite).
- embedded-transitions.js drops the knowledge base's own adopted copies
  before the first swap, identified by the layer-order statement every
  built sub-app stylesheet opens with.

Serving scripts and styles from a separate path prefix was evaluated and
rejected: the gateway dispatches on sec-fetch-dest, not on the path, and a
retargeted build reproduced both failures unchanged.

Tests: relative url() rewrite (unit + dist scan), and an embedded test
that starts on an app page, crosses to another app, and asserts no copy
remains, styles match a hard load and no request fails. Both fail without
the fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
contract/RULES.md is the catalogue: every rule a published docs site is
checked against, by ID (KB-<area>-<nnn>), with its severity, the reason
and the fix. actions/lib/rules.js is the machine side, and the self-test
fails if the two disagree on IDs, titles or severities.

actions/lib/check.js replaces verify-html.js. It parses HTML (htmlparser2)
and CSS (postcss) instead of pattern matching, so markup quoted in prose,
data blocks and article headers are not reported. Every finding names its
rule; errors stop the publish, warnings are annotated with the rule as the
title. The existing checks keep their severity and wording, now prefixed
with the ID; manifest and size failures carry KB-MAN-001 / KB-ART-00x.

New rules, all warnings so no repo pinned to @v1 starts failing: inline
event handlers and javascript: URLs (dead under script-src 'self'), a
site-level <header>, links to non-HTML files (a document request for one
is answered by the host application), !important (beats the kb-app layer
fence), dark mode, scripts/styles/fonts from another origin (CSP), and
fetches of page-relative URLs (resolved against the host page once
embedded).

actions/lib/check-cli.js runs the same checks without publishing, for a
docs repo's PR CI or an agent fixing a repo (--json, --strict).
isThemeBootstrap() moves to the dependency-free src/utils/theme.js so the
build and the checker share one definition.

htmlparser2 and postcss become direct dependencies of actions/ at the
versions its lockfile already carried.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An already-onboarded repo (it has kb-docs.json, or a workflow calling
publish-docs / publish-single-page-docs) now goes down an audit path
instead of onboarding. references/audit.md:

1. check the workflow wiring the checker cannot see (pinned @v1,
   contents: write, the headless variant is what gets built, dist);
2. build what the workflow builds, or download the last release;
3. run actions/lib/check-cli.js from a scratch clone at the pinned
   major, never copied into the repo;
4. fix each finding at its source, errors first, from a table mapping
   every rule ID to its usual fix; never edit built output or patch a
   theme the team does not own;
5. read the site's own scripts for what the checker cannot see (URLs
   built from location, client-side routing) and report those;
6. re-check and report every finding with a status.

Onboarding's Verify step and packaged.md now run the checker instead of
three greps.

Eval 4 (onboarded-mkdocs) is a real mkdocs repo with planted violations:
a workflow that builds the standalone site, an inline dark-mode
bootstrap, an onclick handler, Google Fonts, !important, a page-relative
fetch, a PDF link and a location.pathname-derived base. Built with mkdocs
1.6.1 the checker reports 3 errors and 18 warnings; the fixes audit.md
prescribes bring it to no findings. Two agent runs following only the
skill: the first met 12 of 13 assertions (the miss was an over-strict
assertion, now relaxed) and its feedback shaped audit.md; the second met
all 14.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@oto-macenauer-absa
oto-macenauer-absa merged commit 73227d5 into master Sep 23, 2026
8 checks passed
@oto-macenauer-absa
oto-macenauer-absa deleted the fix/css-isolation-web-fragment branch September 23, 2026 09:21
oto-macenauer-absa added a commit that referenced this pull request Sep 23, 2026
Follow-up to #100. The knowledge base build now runs the same contract
checker the `publish-docs` action runs (`actions/lib/check.js`, rules in
`contract/RULES.md`), on every artifact it installs.

## Why

Not every artifact in a registry went through a current action:
- one published before a rule existed;
- one packed by hand;
- one whose HTML the old v1.0.0 pattern-matching missed, for example a
root-relative URL in a single-quoted attribute or in `formaction`.

The build repairs some findings as it re-hosts a page, so a failing
publish is not the only signal a registry owner needs. They need to see
which docs repo to chase, and for what.

## What

- **`scripts/check-artifact.js`** checks each app as installed into
`apps/`, before inline scripts are hoisted, on every source (prebuilt
and local included).
- Findings are logged grouped by rule: one line per rule with a count
and the first example, then a per-artifact summary.
- Example from the vendored fixture: `KB-HTML-004 ×9, e.g.
user-guide/admin/index.html: …`, then `0 error(s), 28 warning(s)`.
- **A strict build refuses an artifact with an `error` finding.** Strict
is `KB_STRICT`, which the deployment workflow turns on by default. The
error names the rule and the file. Warnings never fail a build.
- **Replaced:** the build's old headless-marker warning, which only
looked at the entry page.
- **Dependencies:** `htmlparser2` and `postcss` are now root
dependencies, pinned at the versions `actions/package.json` pins.
- `check.js` resolves them from `actions/node_modules` when that is
installed, otherwise from the root.
  - CI's E2E job installs only the root.
  - `tests/artifact-checks.spec.js` fails if the two pins drift.

## ⚠️ Deployment impact

Strict is on by default in `build-image.yml`, so after this merges a
deployment build fails if any registered artifact has an error finding.

Artifacts published by the v1 action should pass: the action has refused
these error rules since v1.0.0. The remaining risk is:
- an artifact that did not come from the action;
- a pattern the old checks missed.

To check a deployment registry before rolling this out, run a non-strict
build of it. It logs every finding without failing.

## Testing

- `npm test`: 382 of 382 passed, including the 10 new tests.
- `npm run selftest` in `actions/` passes.
- **Checked the root-only path, as CI's E2E job has it:** with
`actions/node_modules` moved aside, the hermetic build and
`artifact-checks.spec.js` both pass.
- `contract/DEPLOYMENT.md` gains the new strict-rejection row, and
`RULES.md` and `CLAUDE.md` are updated.

Refs #67. The build now logs inline handlers (`KB-HTML-005`) per
artifact; it still does not strip them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant