feat(consolidation): the n0 path becomes the SVG engine of record#61
Conversation
Record the owner's 2026-07-21 direction as a compact, code-agnostic doctrine delta so later sessions stop forcing Web semantics into n0. The topology becomes "one engine kernel, source-native semantic models, one provisional resolved render contract, many hosts." The word that changes is *model*: sources converge downstream at a shared render contract, not upstream at one universal authored model. HTML/CSS + SVG lead as one Web semantic family (one namespace-aware document, one browser-grade cascade; standalone SVG is a different grammar entry into the same machinery). The amendment fixes the shared-boundary field discipline (derived frame data only: identity/provenance, geometry+bounds, transforms/clips/masks/ effects/isolation/painter-order, ordered paint stacks, shaped-text, resource refs + env revisions; never source syntax, ASTs, n0 ops, raw XML, backend objects, or round-trip promises), draws the new-path forbidden edges (no legacy import adapters, no legacy node model, no backend escape hatch around the kernel, adopt-by-contract-not-copy), and names the three questions deferred to a later evidence spike (n0's join point, a generic frontend trait, the leaf-vocabulary seat). What it supersedes: the "one model" reading of goal.md and the charter's phase ordering where they conflict. What it does NOT supersede: patrol-before-drop, oracle discipline, frozen surfaces, the two gate classes, and the FLIP seal. Wired into the cluster: index.md callout + superseding banners atop goal.md and topology.md. Gate evidence (docs step): git diff --check clean; all cross-links resolve to existing sibling docs; no code paths touched; docs-wg discipline (code-agnostic doctrine delta, defers to golden docs, states only the delta, cluster hub updated).
…nt-end
The first internal, breakable architecture step of the Web-first track
(docs/wg/consolidation/web-first.md). Three real producers reach one
source-neutral downstream:
HTML-with-inline-SVG ─┐
standalone SVG ───────┼─▶ rframe::Frame ─▶ drawlist ─▶ painter ─▶ pixels
n0 (canary) ──────────┘ (source-neutral (private) (one Skia
resolved contract) module)
- crates/rframe: the provisional source-neutral resolved render contract
(`Frame`, Skia-free), its private drawlist (Skia-free), and the one Skia
painter (replays the drawlist; records no SkPicture). Adopts neither cg
nor the n0 vocabulary — the leaf-vocabulary seat is a deferred owner
decision, kept open. Geometry reuses math2.
- crates/websem: the Web semantic front-end. One namespace-aware document
(csscascade::DemoDom) + one Stylo cascade + one SVG semantic compiler.
Inline SVG is compiled in place from the shared document (no
serialize-and-reparse); its descendant style comes from the surrounding
cascade. Standalone SVG is a second grammar entry into the same machinery.
- n0 canary (rframe/tests/n0_canary.rs): a one-rect n0-model document is
resolved by n0's own resolver and lowered into the same rframe::Frame →
drawlist → painter. Proves source-neutrality; adds no n0 XML features.
- fixtures/web-first: the fixtures + a committed Chromium oracle (DSF=1)
+ provenance + a reproducible bake script.
KEY FINDING (the next crux). The workspace's Stylo is the *servo* engine,
which drops the gecko-only SVG paint longhands (`fill`, `stroke`, …) — they
are absent from ComputedValues, so `fill` cannot come from the shared
cascade. Verified empirically and from source. The cascade DOES carry
`color`, which crosses the HTML→inline-SVG boundary; the fixture uses
`color` + SVG `fill="currentColor"` to demonstrate the exact owner intent
honestly. Making the shared cascade model SVG paint properties is an owner
decision (gecko engine vs custom-property registration), filed, not shimmed.
Patrol ledger (captured essence — legacy know-how referenced, not copied):
- Scope: the legacy Web render path is crates/grida/src/htmlcss (+ /svg).
It (a) serialize-reparses inline SVG, (b) uses a temporary SVG-only CSS
matcher instead of Stylo, (c) outputs an SkPicture. None of that is
reused; the new path is clean-room and avoids all three.
- Caveats re-homed: the SVG rect→paint semantics (Blink-modeled in the
legacy renderer) are re-derived minimally against the contract; the
"replay directly, no SkPicture" painter discipline (n0::paint) is adopted
as a principle, painter written fresh.
- Deliberate drops (fail explicitly, never shimmed): gradients, strokes,
masks, clips-by-clipPath, filters, text, `<use>`, markers — every
unsupported construct returns an explicit websem::CompileError.
- Nothing legacy deleted or replaced; this only adds.
- Known caveat: csscascade's process-global document slot forces a compile
mutex (fights "many hosts"); documented, parked.
Gate evidence (proportionate to the slice; no scoreboard score produced —
the sealed scoreboard is never invoked):
- Rust tests: `cargo test -p rframe -p websem` — 8 passed, 0 failed.
- Dependency-direction/architecture: rframe contract+drawlist locked
Skia-/producer-free; websem locked off legacy import/node/fbs/backend/IO.
- Equivalence: inline-SVG-in-HTML and standalone SVG compile to a
byte-identical SVG-local rframe::Frame.
- Reftest vs a committed Chromium oracle (DSF=1, integer-aligned 64×64):
rframe's render probes exactly to Chromium's pixels; no similarity score.
- Determinism: two renders are byte-identical.
- n0 canary renders green through the same drawlist + painter.
- cargo fmt --check clean; cargo clippy --no-deps clean; git diff --check clean.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedToo many files! This PR contains 203 files, which is 103 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (190)
📒 Files selected for processing (213)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Turns the Web-first prototype's next crux from a one-liner into a decision-ready finding, grounded in empirical evidence. The crux: HTML and SVG must share one browser-grade cascade, but the Stylo build the workspace compiles (the servo engine) omits the SVG paint properties, so `fill`/`stroke` cannot come from the shared cascade. Evidence gathered (throwaway spikes, not committed): - E1: of the 46 SVG-struct longhands in Stylo 0.16, 44 are gecko-only and absent under servo — the whole fill/stroke/marker/stop/mask/geometry set; only clip-path and mask-image (shared box props) survive. - E2: CSS custom properties DO cascade HTML->inline-SVG and read back under servo — but as untyped token strings (no computed-value processing, no currentColor/url() paint-server/type semantics). - E3: the gecko engine pulls bindgen/mozbuild/nsstring (a Gecko build tree) — not viable in a pure-Rust workspace. - E4: the SVG value types compile unconditionally under servo; only the property declarations are engine-gated — so a stylo fork is plausible but unverified at build depth and high-maintenance. The doc lays out five options (gecko engine / stylo fork / custom-property carrier / status-quo attribute read / track upstream) with feasibility+cost+tradeoffs, recommends keeping the status quo near-term, not adopting the lossy carrier as the cascade of record, and scoping a timeboxed stylo-fork feasibility spike as the path most faithful to "one browser-grade cascade" — and files it as an owner registry decision. Linked from the Web-First Amendment and the cluster index. Gate evidence (docs step): git diff --check clean; all cross-links resolve; claims E1/E2 reproduced by parsing stylo's longhands.toml and by running a csscascade cascade spike (both removed after); no code paths touched.
|
Added a decision-ready finding for the identified next crux — SVG paint in the shared cascade ( Empirically established: 44 of 46 SVG-struct longhands in Stylo 0.16 are gecko-only and absent under the servo engine the workspace compiles — the whole The finding evaluates five options (gecko engine · stylo fork · custom-property carrier · status-quo attribute read · track upstream) with feasibility/cost, recommends keeping the status quo near-term and not adopting the lossy custom-property carrier as the cascade of record, and files how SVG paint enters the shared cascade as an owner registry decision. Still a draft; no merge without your GO. |
Advances the amendment's deferred join-point crux (does n0 emit the common resolved contract, or join only at the drawlist boundary?) into a decision-ready gap analysis grounded in n0's real downstream types. Reframes the binary A-vs-B into a per-fact boundary, classifying each fact in n0's ItemKind stream: - opacity scopes / clips / painter order / geometry / bounds: structurally neutral -> converge HIGH (common contract). - paint stacks / strokes: neutral concepts carried as n0-model value types -> HIGH, but coupled to the (deferred) leaf-vocabulary-seat decision. - corner smoothing (squircle): an authoring parameter resolved in n0's painter -> must be resolved to neutral geometry BEFORE the contract, else it leaks an n0/authoring concept (forbidden). - shaped text: bound to n0's private font registry + own oracle -> the DECIDING fact; needs a neutral shaped-text + font-key boundary that two producers can emit, which does not exist yet. Recommendation: do not take a uniform decision; record a per-fact boundary; couple it with the leaf-vocabulary seat; and gate the deciding text spike on a second shaped-text producer (per "two real producers first"). Files it as an owner registry decision, coupled with the leaf-vocabulary seat. Explicitly NOT ripe to decide now. Linked from the amendment's "n0's join point" bullet and the cluster index. Gate evidence (docs step): git diff --check clean; cross-links resolve; classification grounded in crates/n0/src/drawlist.rs ItemKind and the private TextFontRegistry; no code paths touched.
|
Advanced the second deferred crux — where n0 joins the shared downstream ( A gap analysis against n0's real
Recommendation: don't take a uniform decision; record a per-fact boundary; couple it with the leaf-vocab seat; gate the deciding text spike on a second shaped-text producer (per "two real producers first"). Explicitly not ripe to decide now. Filed as an owner registry decision. Still a draft; no merge without your GO. |
Makes the prototype runnable from the command line, answering "what can we do from a CLI today." Previously rframe+websem were library-only, exercised only through cargo test. - rframe::render_png(&Frame, w, h) -> Vec<u8>: the downstream plus PNG encoding, for a host that just wants a file to write. - crates/websem/examples/render.rs: a thin host (goal.md's "CLI is always a thin host" — meaning lives in the engine). It reads an SVG or HTML-with-inline-SVG file, compiles via websem into the source-neutral rframe::Frame, renders + encodes via rframe, and writes a PNG. Argument parsing, file I/O, and raster size are the host's only concerns. Usage: cargo run -p websem --example render -- <input.svg|input.html> <out.png> [WxH] Verified end-to-end: - svg-currentcolor-rect.svg -> 64x64 PNG, all 4096 px #16a34a. - html-inline-svg-...html -> 128x128 PNG, the 64x64 SVG green on a transparent canvas. - Unsupported constructs fail explicitly (exit 1, clear message), no shims: <circle> -> "unsupported element <circle>"; fill="url(#g)" -> "unsupported fill value \"url(#g)\"". Scope: the host is an example, not a productized CLI — a standalone svg->png CLI host crate (goal.md's render ecosystem, #13) is the next step if wanted. The host only exercises the current slice (solid-fill <rect>); everything else is an explicit error. Gate evidence: cargo fmt --check clean; cargo clippy --no-deps (lib+tests+ examples) 0 warnings; cargo test -p rframe -p websem 8 passed 0 failed; git diff --check clean.
Answers "do we have a real-world-like HTML webpage design to render?"
Honest framing: the Web-first slice is not an HTML/CSS layout engine — the
HTML entry compiles the inline <svg> and ignores surrounding HTML (a page
with no inline SVG errors: "no <svg> element in document"). Real HTML/CSS
webpage rendering lives in the legacy crates/grida htmlcss renderer, which
the amendment forbids for this path.
What this adds is a webpage *design* (header / hero / sidebar cards / bottom
row / footer) expressed as 27 solid-fill inline-SVG <rect>s inside an HTML
document — renderable by the current slice, and exercising the real feature:
the brand color authored once in the HTML <style> `.brand` rule cascades into
the inline SVG via `fill="currentColor"`.
- fixtures/web-first/html-webpage-mockup.html — the fixture.
- crates/websem/tests/webpage_mockup.rs — a data test (no render) asserting it
compiles to a multi-rect layout and that the brand purple (#4a3aa7, authored
only in the HTML <style>) resolves onto an inline-SVG rect.
- Renderable end to end:
cargo run -p websem --example render -- \
fixtures/web-first/html-webpage-mockup.html /tmp/page.png
-> 640x400 PNG, 27 nodes; header + hero accent are the cascaded brand purple.
Gate evidence: cargo test -p rframe -p websem 9 passed 0 failed; clippy
--no-deps 0 warnings; fmt --check clean; git diff --check clean.
Adds a seed corpus of genuine, self-contained real-world webpages that serve as the harness the Web-first engine is measured against — the target, not a demonstration of current capability. Rich by design (real semantic HTML, real text content, images, real CSS); "minimal, one concept per file" is the rule for unit fixtures, not for a real-world page corpus. Honest framing: the Web-first engine renders NONE of these yet (websem+rframe handle solid-fill SVG <rect> only). These are the ground it grows toward; the ground truth is Chromium. Same relationship fixtures/test-html has with the legacy renderer — an input corpus + a browser oracle, not a pass today. - fixtures/web-first/pages/article.html — blog/magazine article (hero SVG illustration, drop-cap, prose, blockquote, table, syntax-highlighted code, author card, related grid, footer). - fixtures/web-first/pages/landing.html — SaaS landing page (product-dashboard SVG, feature grid, stats band, testimonial, 3-tier pricing, gradient CTA). - fixtures/web-first/pages/docs.html — docs page (3-column nav/content/TOC, callouts, code blocks, API table, pager). - bake_reference.ts — bakes Chromium reference renders (DSF=1, 1200px wide, full-page) with all http(s) aborted, so a page needing any external resource renders broken. README + .gitignore. Self-containment verified: all imagery is inline <svg>, all CSS inline, system fonts, ZERO external resources — Chromium rendered all three with the network off and blocked 0 external requests. Full-page sizes: article 1200x6561, landing 1200x4621, docs 1200x3929. The reference renders are regenerable + environment-pinned (system-font text), so they are gitignored rather than committed — the refbrowser discipline (inputs + generator committed; browser oracles rendered on demand). Gate evidence: self-containment audit (0 external URLs / <link> / <script src> / @import / url(http) / non-data <img>); Chromium bake blocked 0 external requests; git diff --check clean.
|
Added a real-world webpage harness — Three genuine, self-contained real webpages as the target the Web-first engine grows toward (not a demo of current capability): Honest framing: the engine renders none of these yet (websem+rframe do solid-fill SVG Self-containment is verified, not assumed: zero external URLs /
|
Patrol / captured-essence ledger: - Preserved the one-document/one-cascade -> Frame -> private drawlist -> painter route and the tiny n0 source-neutrality canary. - Kept the current standalone entry only as html5ever foreign-content scaffolding; no legacy adapter, node model, grida.fbs, frozen wasm surface, or backend escape hatch was introduced or changed. - Replaced silent malformed-token filtering and non-uniform viewBox stretching with explicit proving-shell rejections. Added five rejection fixtures; arbitrary SVG remains outside the admitted capability claim. - Made Skia optional at the rframe boundary, kept the drawlist crate-private, and corrected identity from stable to frame-local until a real incremental producer forces provenance. Gate evidence: - cargo test -p rframe -p websem: 17 tests passed, including the n0 canary and 5 viewport rejection probes. - cargo test/check -p rframe --no-default-features and RUSTDOCFLAGS=-Dwarnings cargo doc -p rframe --no-default-features --no-deps passed. - cargo tree -p websem --edges normal -i skia-safe reported no normal backend edge. - cargo clippy -p rframe -p websem --all-targets --no-deps, cargo fmt --all -- --check, and git diff --check passed. - No scoreboard command or score was produced; FLIP remains sealed.
Patrol / captured-essence ledger: - Replaced the six-pixel sample with a closed manifest and full RGBA comparison for every root primitive; retained the existing Chromium pixels and made the baker refuse to overwrite a differing baseline. - Added committed oracles for both HTML-authored primitives and a non-zero-origin, uniform-scale viewBox fixture, preserving the inline/standalone currentColor proof. - Kept article/landing/docs as target-only pages. The page baker now creates a fresh output directory, records encoded PNG dimensions and environment provenance, blocks external resources, and double-captures on fresh pages; it does not claim an engine reftest or page capability. Gate evidence: - Chromium 149.0.7827.55 verified all 4 enumerated primitive oracles twice; cargo test -p websem --test reftest_oracle passed closed enumeration, provenance hashes, zero differing RGBA pixels, and double-run CPU/PNG determinism. - The thin CLI rendered svg-viewbox-uniform-offset-rect.svg at 80x40 through websem -> Frame -> the shared painter. - The page baker deterministically captured article 1200x6561, docs 1200x3929, and landing 1200x4621 with zero external requests. - pnpm -C packages/grida-reftest run typecheck, cargo fmt --all -- --check, and git diff --check passed. - No similarity score, scoreboard run/report, or FLIP pass claim was produced.
Patrol / captured-essence ledger: - Preserved every non-conflicting charter obligation while making the ratified Web-First Amendment sequencing explicit; the legacy-adapter route remains superseded only for the new Web path. - Registered D-L for SVG paint in the shared cascade and D-M for the coupled leaf-vocabulary seat / per-fact n0 join point. No option, registry outcome, merge, or owner GO was inferred. - Corrected the SVG-paint finding: the scaffold reads direct fill attributes only, SVG-namespace style collection and presentation hints are absent, and a Stylo longhand patch alone is insufficient. - Kept the one-rectangle n0 canary quarantined as an invariant probe and the solid-fill leaf provisional; stroke, gradient, and text vocabulary remain owner-gated. Gate evidence: - The charter now sequences proving shell -> SVG vector -> shaped text -> resources -> CSS box/layout -> page composition with explicit entry and exit evidence. - D-L and D-M appear consistently in the index, amendment, findings, charter registry, and glossary; all new relative link targets exist. - git diff --check passed. This documentation commit changes no runtime behavior. - FLIP remains unratified; no scoreboard run/report/score or capability-pass claim was produced.
Patrol ledger: preserve the Stylo cascade and computed-value mappings, Taffy layout caveats, mature SVG semantics, text/resource behavior, fixture/oracle conditions, and n0 frame/time/damage/resource invariants. Do not adopt the leaking process-global DOM, context-free Stylo handles, ambient fixed environment or Grid policy, inline-SVG serialize/reparse, the temporary SVG matcher, backend objects/pictures in semantic contracts, legacy nodes, or a third kernel. Deliberate drops: none. Decision evidence: inventory the mature renderer, n0 chassis, and rframe proving shell as three distinct paths; correct the HTML/SVG gate census; stage D-M into vector and text evidence; define exact high/low join ownership; classify every n0 drawlist compiler read; and require an independently constructed vector-input, multi-frame, damage/cache, raster, and mixed-composition spike before owner GO. Gate evidence: cargo test -p grida --lib -- htmlcss::tests --test-threads=1 (155 passed); cargo test -p grida --lib -- htmlcss::svg --test-threads=1 (47 passed); htmlcss SVG checkpoint/architecture and HTML import snapshot/architecture integration tests passed; pnpm fmt:check; pnpm --filter www types:check; CI=1 NEXT_TELEMETRY_DISABLED=1 pnpm --filter www build (Turbopack, 181 pages); git diff --check. Two adversarial topology reviews are clean. No production behavior changed and no scoreboard run/report/score was produced.
Patrol / captured essence: - Move all 56 mature HTML/CSS/SVG source files and both SVG gate suites from grida into the unpublished htmlcss crate; deliberate renderer drops: none. - Preserve Stylo cascade, Taffy layout, paragraph shaping, resource providers, SVG DOM/style/layout/paint, and direct-Skia behavior. - Move the Markdown stylesheet byte-for-byte to its sole renderer owner and remove the stale legacy symlink. - Keep grida as a compatibility consumer; only parse_and_style and styled_of cross the crate-private importer seam. - Keep the normal websem edge backend-free; its thin example host now renders .html/.htm/.svg files to deterministic CPU PNGs. - Admit no legacy node/SceneGraph/FBS or speculative n0/rframe semantics into htmlcss. Known transitional debt: - The mature renderer still returns SkPicture, uses a process-global Stylo slot, serializes/reparses inline SVG, and retains a standalone-SVG matcher. - The CLI currently accepts one self-contained file, explicit raster size, and PNG output only; directories, external resources, and other formats remain parked. Gate evidence: - python3 bin/check-legacy-pixel-sweep 5c36067: PASS, 65/65 declared L0.exact PNGs byte-identical, corpus f1892dcdab29442e. This is an extraction A/B gate, not a Chromium conformance score. - cargo test -p htmlcss -- --test-threads=1: 216 pass. - cargo test -p websem: architecture/equivalence/oracle/viewport tests plus 2 discovered CLI acceptance tests pass. - Dedicated <=3-color SVG circle and HTML flex+inline-SVG probes pass; complex L0 fixtures remain integration smoke only. - grida importer architecture/snapshot and legacy seam locks pass; rframe/websem and n0 canaries remain green. - Affected cargo check and clippy --no-deps, cargo fmt --all -- --check, cargo metadata --locked, and git diff --check pass. - Normal cargo run -p websem --example render produced the pre-extraction-identical SVG PNG hash 41ad38c8e5a7dc1b1423276e2a083e13b0b1cd5bf6032d2e369369f6bb6b7e28. - FLIP remains unratified; no scoreboard run, report, score, or landed-capability claim was made.
Patrol and boundary: - websem is the backend-free Web semantic producer/proving shell, not the product host - htmlcss already exposes the required render/render_svg provider seam; no producer contract change was needed - n0_cli owns arguments, source I/O, host fonts, CPU surface, and PNG encoding only; no legacy, websem, rframe, or speculative n0 production edge Captured essence: - physically move the HTML/SVG executable and its strict extension/size/PNG contract to the n0 binary - preserve system-font fallback, self-contained resource policy, transparent CPU raster, deterministic encoding, exact probes, and file round-trip - retire the websem example while naming the direct htmlcss/SkPicture seam as transitional - retain now-unused websem htmlcss/skia dev declarations temporarily: the mandatory crate-cut Cargo.lock additions-only gate requires their subtraction after this cut lands Gate evidence: - pre-move websem example: 2 tests passed - post-move n0_cli: 2 render tests + 3 architecture locks passed - standalone SVG, HTML with inline SVG, and landing page outputs cmp byte-identical before/after (sha256 41ad38c8..., bd1c3b85..., 93d93bea...) - declared legacy sweep: 65/65 L0.exact PNGs byte-identical; corpus f1892dcdab29442e - cargo test/check/clippy --locked for n0_cli, websem, and htmlcss passed - cargo fmt --check, cargo metadata --locked, and git diff --check passed - no scoreboard run/report/score; FLIP remains sealed
Patrol ledger: - Exact Stylo 0.16 exposes only 2 of 46 SVG-struct longhands under Servo; Gecko imports Mozilla build dependencies outside the standalone Cargo constraint. - An isolated Servo-only fill/stroke patch required one additional pointer-sized computed-style slot and no Gecko dependency. - The existing cascade had three independent ingress gaps: SVG-namespace styles, presentation hints, and strict SVG/XML document entry. Captured essence: - Record typed paint values, defaults, inheritance, currentColor child resolution, parse-invalid versus computed-invalid behavior, and typed URL/fallback preservation without claiming resource binding. - Record presentation precedence, document-order SVG styles, strict XML namespace/case behavior, direct SVG root, and explicit non-HTML document mode. - Quarantine the unproven remaining longhands, production encoding/DTD/base/resource policies, and fork upgrade ownership. D-L remains owner-only. Gate evidence: - Patched Stylo 0.16 cargo check --no-default-features --features servo: PASS. - HTML/inline-SVG cascade probe: native fill/stroke, precedence, inheritance, currentColor, invalid values, defaults, presentation hints, and SVG style order: PASS. - Strict standalone SVG/XML probe: direct root, namespaces, case sensitivity, SVG style, inheritance, malformed-input rejection, non-HTML mode, and :root: PASS. - cargo test --locked -p n0_cli: 5/5 PASS. - cargo fmt --all --check, locked metadata, and git diff --check: PASS. - No scoreboard run, report, score, capability claim, or registry decision.
Patrol: Stylo 0.19.0 predates servo/stylo#383. Its exact official merge a64923b5d5c67313c81c5056f5e30ec0babb04d6 supplies 11 basic Servo SVG paint longhands; 24 SVG-struct longhands remain Gecko-only. Pin every directly consumed Stylo-family crate at one immutable official source, refuse packaging while registry semantics lag, and lock provenance. No private fork, floating branch, or registry fallback. Cut/adopt: adapt the upstream AttributeProvider, Device-environment, and Flex API changes. Declare one deterministic static-desktop pointer profile and explicitly quarantine newly parsed unsupported radial clip extents. Production SVG presentation-hint ingress and computed-paint consumption remain untouched; D-L decides dependency provenance only. The fixed 1280x720 cascade environment remains the next host seam. Captured-essence ledger: owner GO on 2026-07-23 takes D-L for official upstream, preferring the first release containing servo/stylo#383 and using this exact merge until then. Upstream font quantization changes 8 snapshot values from 11.900001 to 11.90625 and 10 from 16.38 to 16.375; every affected change precedes the layout sections, whose bytes remain identical. No deletion or legacy behavior drop. Gate evidence: full locked tests pass for csscascade, htmlcss, websem, n0_cli, and grida; n0-model and n0 canaries pass; the focused SVG-paint and provenance probes pass; grida-canvas-wasm checks; affected locked check and no-deps clippy pass; fmt and diff checks pass. The declared 65-fixture L0.exact A/B sweep is byte-identical, corpus f1892dcdab29442e. Basic SVG, inline-SVG HTML, and the landing page each render twice byte-identically and match their pre-pin hashes 41ad38c8, bd1c3b85, and 93d93bea. No FLIP-sealed scoreboard command was run.
Patrol ledger: - Production and tests consume csscascade::dom + adapter + cascade only. - The retired rcdom/tree stack had no workspace consumers; its StyleRuntime returned default values. - The 1,897-line experiment duplicated the promoted adapter/cascade implementation. Captured essence: - Arena parsing remains in dom; Stylo trait integration remains in adapter; stylesheet resolution remains in cascade. - resolve_and_print remains as the one diagnostic over the live path. - README and ARCHITECTURE now record the actual boundary, process-global lifetime defect, and explicit-environment gap. Deliberate drops: - Unused RcDom wrapper, stub styled-tree/builder/serializer, four obsolete examples, and accidental Hello World binary. - No production API consumer, fixture, rendering behavior, dependency, or lockfile entry changed. Gate evidence: - cargo test --locked -p csscascade --all-targets - cargo test --locked -p htmlcss -p websem -p n0_cli -- --test-threads=1 - cargo test --locked -p grida --test html_import_architecture --test html_import_snapshot -- --test-threads=1 - cargo check --locked -p csscascade -p htmlcss -p websem -p n0_cli -p grida -p grida-canvas-wasm - cargo clippy --locked --no-deps -p csscascade --all-targets - cargo clippy --locked --no-deps -p htmlcss -p websem -p n0_cli -p grida - Legacy pixel sweep: 65/65 L0.exact PNGs byte-identical; corpus f1892dcdab29442e - Repeated CLI hashes unchanged: 41ad38c8e5a7dc1b, bd1c3b859b09e3ba, 93d93bead64e936e - cargo fmt --all --check; git diff --check No scoreboard run, report, or score was produced.
Patrol ledger: - Official Stylo now exposes typed basic SVG paint, but websem still reads a direct fill attribute and htmlcss SVG still uses its temporary matcher. - Inline SVG in the mature renderer is serialized and reparsed; standalone SVG uses the same storage type, not the shared Stylo session. - L0.exact uses threshold 0 with AA exclusion and a content mask, so 1.0 is configured Chromium similarity rather than raw PNG byte identity. Captured essence: - Code comments and fixture guidance now distinguish dependency provenance from production paint ingress/consumption. - n0_cli is named as the host of the extracted mature renderer, not evidence of chassis convergence. - The legacy A/B sweep remains genuinely byte-for-byte and now hashes canonical render-relevant suite semantics plus every declared input byte. Deliberate drop: - Only the human-facing suite description is excluded from corpus identity. Every other existing or future manifest key remains fail-closed in the canonical hash. - No render semantics, fixture input, oracle, threshold, dependency, or output changed. Gate evidence: - cargo test --locked -p websem -p htmlcss -p n0_cli -- --test-threads=1 - cargo clippy --locked --no-deps -p websem -p htmlcss -p n0_cli - Legacy pixel sweep vs ee2901c: 65/65 L0.exact PNGs byte-identical; semantic corpus 3a55fc5e17844af6 - JSON parse checks; check-legacy-pixel-sweep --help - cargo fmt --all --check; git diff --check No scoreboard run, report, or score was produced.
The resolved contract grows its second geometry kind and websem compiles the SVG2 path-data grammar into it. Every answer in that grammar was measured against Chromium 149 rather than read off a spec, and two constructs refuse by name because measurement said they must. rframe gains a checked `PathData`: a canonical absolute command stream (move/line/quad/cubic/close), the fill rule, the tight local bounds solved once so producer and consumer cannot disagree, and whether every contour closed. It carries no arc command — an arc is authored syntax whose lowering decides the pixels, so that choice stays with the producer that can verify it against a browser — and no rational conic, because the one producer that needs one is not admitted yet. websem's `svg_path` resolves the grammar with the donor's question list and Chromium's answers: a number consumes at most one trailing comma, a dot must carry a digit, and the canonical form is reached by normalizations each measured pixel-neutral *on anti-aliased geometry* first. `fill-rule` is consumed as a typed cascaded value, so SVG2 precedence, inheritance and keyword case-insensitivity come from the one cascade; csscascade admits it as a presentation hint. Two deliberate divergences, both declared. A malformed `d` refuses the whole path at its byte offset instead of rendering Chromium's valid prefix. And the elliptical arc refuses: Chromium rasterizes an arc through the same conics as an `<ellipse>` (byte-identical over every shared row), while Blink's cubic *normalizer* — rendered by Chromium itself — differs from Chromium's own `A` by 77 pixels at up to a 170-per-channel delta. Patrol gaps this rung had to close before it could admit anything: the CSS `d` property (Chromium paints a stylesheet's `d: path(…)` in place of the attribute), the marker properties, `vector-effect`, `shape-rendering`, CSS motion path, the `all` shorthand, and vendor aliases of names already listed. The CSS scan now strips comments and vendor prefixes, refuses an escaped property name outright, and reads the sheet the cascade actually compiles rather than each text node — every one of those was a measured leak.
Thirteen new cells, all byte-exact: one triangle spelled four ways (closed, unclosed, relative, H/V — four byte-identical oracles), a cubic, an `S` continuation, a `Q`+`T` pair, both fill rules on a self-intersecting star plus the rule inherited from a `<g>`, two subpaths, a path in a scaled group, and a `d` that draws nothing. The corpus's declared departure narrows again. Straight edges already agreed byte-exactly under rotation; now the cubic, smooth-cubic and quadratic cells do too. Curves as such are not the boundary — the weighted rational conic is, and no path cell carries a tolerance. `svg-path-closed-move-only-contour.svg` earns its place: `M x y Z` is a zero-length *closed* contour, and dropping it moves 96 pixels of the surviving triangle. Its coordinates are fractional on purpose, because the integer axis-aligned geometry the rest of the corpus uses hides exactly this class of difference. `unsupported/` gains the arc, a malformed `d`, path data with no leading moveto, a trailing-dot number, a stylesheet `d`, `pathLength`, and a real `<marker>` reference — with each row saying whether its refusal is over-refusal (`pathLength`) or load-bearing (`marker-*`).
Records what was measured rather than what was expected: the arc's conic finding (Chromium's `A` is byte-identical to its `<ellipse>`, and Blink's cubic normalizer is not what Chromium paints), the AA boundary narrowing a third time to the weighted rational conic alone, the six CSS patrol leaks, and the corrected account of which Stylo properties are gated and how. The sharpest entry is the round's own finding: `M x y Z` is a zero-length closed contour, dropping it moves up to 96 pixels of the *surviving* geometry, and the law that blessed the drop passed only because its coordinates were integer and axis-aligned. A fuzz of 40 fractional-coordinate triangles diverged 40 out of 40. The claim "measured, not assumed" was the defect — the measurement had been taken on geometry that cannot show the difference. One finding is recorded rather than fixed: a SMIL animation of a consumed attribute is active at load in Chromium while a Base render paints the authored value and declares nothing. Systemic, predates this rung, and gets its own.
…okes rung A stroke becomes a second painted fact per node, and the tiger's last blocker goes with it. rframe gains a `Stroke`: paints, a width in the node's local space, a cap, a join, a miter limit — and two deliberate absences. There is no alignment field, because a Web stroke straddles its geometry and that is the only alignment a Web source can express; an aligned stroke grows the type when a producer needs one. And there is no invisible stroke: construction resolves a zero width or an empty paint stack to `Ok(None)`, so no consumer re-derives "is this visible". `FrameNode.bounds` stays the *geometry's* bounds — ink lies outside it, and `Stroke::outset()` states how far, counting both the join's miter room and the square cap's corner at radius·√2. websem reads every stroke property as a typed cascaded value, which buys `8px`, `0.5em`, inheritance through a `<g>`, stylesheet overrides, keyword case-insensitivity, and the right fallback for a negative width (the declaration fails its non-negative grammar, so the cascade drops it — which is what Chromium paints). csscascade admits the five stroke properties as presentation hints, plus `font-size`, which nothing paints yet but which is the basis an `em` length resolves against; a bare-number attribute value is retried as `px`, reproducing the SVG attribute mode Blink parses these in. `<line>` needed no geometry kind: it compiles to a two-command path. Chromium's `<line>` is byte-identical to the equivalent `<path>` (measured), a line has no interior for a fill to cover, and the caps, joins and zero-length rules then come out identical for free. Two rules the measurements forced. A zero-extent `<rect>`/`<circle>`/`<ellipse>` disables rendering of the element *including its stroke* — a naive stroke of a zero-extent box would draw a line — while a `<path>` is exempt, because a zero-extent path is a zero-length segment that strokes as a dot. And a dash array that would paint nothing (`none`, all-zero, invalid) is admitted rather than refused, because Chromium renders those solid. What still refuses, by name: `stroke-opacity` and dashing (the compositing and dashing halves), a percentage width (the normalized-diagonal basis), a width in a viewport-relative or font-metric unit (this build's device is pinned at 1280x720 and its font metrics are placeholders, so `1vw` computed 12.8 where Chromium paints 0.64), a width whose reach cannot be represented, and paint servers and context paints. A negative box extent now disables that one element instead of aborting the whole render, which is what Chromium does.
Twenty-four cells (corpus 46 → 70), 23 of them byte-exact: the centred pen, the stroke over the fill, the cascaded width in every spelling, inheritance through a `<g>`, a stroke on every admitted geometry, all three caps including the zero-length dot, all three joins, the miter-limit bevel, the elliptical pen a non-uniform scale produces, a `<line>` whose fill paints nothing, and a zero-extent rect that paints nothing at all. The one exception is the round join at a *closed* contour's closing corner: 4 pixels at delta 3, declared with that join's arc as its boundary. Every cap, every other join, and both the stroked circle and ellipse are byte-exact — so strokes land inside the corpus's declared rational-conic departure and mostly below it. `unsupported/` gains the five refusals: `stroke-opacity`, dashing, a percentage width, `vector-effect` and `paint-order`. The last two were provably inert while strokes refused and became load-bearing the moment strokes painted — which is the reason the earlier rungs kept them patrolled.
… register The tiger renders: 240 paths, 241 groups, --strict, exit 0, zero declared degradations, both admissions byte-identical, and 96.27% of its pixels byte-identical to Chromium's render of the same file. It needed exactly what the last four rungs admitted and nothing else, which is what makes it a milestone rather than a target. Records the rung's own findings alongside it: the contract's second painted fact and the two absences that keep it honest, `<line>` as a two-command path, the zero-extent rule as an *element* rule, and the dash array that paints nothing being admitted because Chromium renders it solid. The adversarial round's three unit-basis defects get their own paragraph, because they are one lesson: this rung consumed the first cascaded *length*, and a length is only as good as a basis this build may not have. A viewport-relative width computed 12.8 where Chromium paints 0.64; `ex` resolved from placeholder font metrics; and `em` was right from a stylesheet and wrong from a presentation attribute. My law had tested only unitless, `px` and `em`-at-the-default. The tiger file itself is deliberately not committed: it is AGPL and this repo is Apache-2.0/MIT, which is what fixtures/local/ exists for. Capability stays gated by the committed byte-exact corpus; the tiger is a measurement, reproducible from one command.
…nd sampled The tiger proved the rung ladder end to end but cannot be committed: it is AGPL and lives untracked in fixtures/local/. So the capability it demonstrated had no committed cell, and nothing in the corpus exercised the rungs *together* — 70 cells, each isolating one construct. The cub is an original drawing covering the same feature list in 17 materialized nodes instead of 240: a viewBox-only root, a container with a transform and a nested <g>, fill/stroke/stroke-width/stroke-linejoin/ stroke-linecap inherited through both, cubics and quadratics, relative command runs, multi-subpath paths, round caps and joins, fill="none" and stroke="none", rects in a group, and a <line>. It renders --strict with zero declared degradations at zero differing pixels, and it is drawn deliberately without a <circle> or <ellipse> so the declared conic departure stays where the strokes rung left it: near-elliptical shapes are cubic approximations, which bake byte-exact. It ships as a pair — the animated document and its static Base projection — so one scene gates both the Base view and four exact-nanosecond samples. Five frames, all byte-identical to Chromium. The sampling corpus is therefore now plural: cases.json carries a fixtures array (schema 1) with each fixture's declared frame shape, the baker loops it with a per-fixture initial viewport and validates each declaration structurally — including that a fixture's authored value differs from its first sample, so Base and Sample(0ns) can never silently coincide — and each fixture's oracles live under chromium/<id>/. The rect-x pixels moved directories and the bake re-verified them byte-identical, which is the proof the rename lost nothing.
…shape The suite laws now iterate cases.json's fixtures, taking each fixture's declared dims as its initial viewport (the cub's root is viewBox-only, so passing anything else would render a differently-sized scene than the oracle) and reading its animated node by the declared index rather than a hard-coded nodes[1]. The declaration is load-bearing: a fixture that stops materializing an element, or that moves its animated rect, fails at the node-count assertion instead of quietly weakening every law below it. The damage law generalizes to "one node changed, over the union of where it was and where it went", computed from the frames themselves; the rect-x rectangle stays pinned by hand beside it, because a union derived from the same frames the diff came from would be satisfied by a bounds regression that moved both. The host proof adds the property a composition makes checkable: every pixel that differs between the cub's Base frame and a sample lies in the animated rect's own rows. Sixteen nodes of curves and strokes render identically at every time, both admissions are byte-identical, and the frozen pair is the same frame. Negative controls: perturbing the declared node index, and a one-digit fill change in the source, each fail the laws they should. Also drops an unused import the groups rung left in groups_contract.rs — the only warning in the test gate.
…found The addendum records what the scene fixture is for, what its two absences say about the admitted slice (the animated element must be a top-level rect; a sampled document can reach currentColor by neither door, since <style> and style= are dynamic-inventory blockers and color= is a declared boundary), and why it carries no <circle> or <ellipse>. It also records a defect found while verifying the cub at a second size. The scene gates byte-exact at 96x96; at 48x48 — a 1x viewport mapping — 242 of 2304 pixels differ from Chromium. Bisected: a closed contour with a non-butt cap at a ~1-device-pixel stroke width. A cap cannot exist on a closed contour, and Chromium's raster is cap-invariant there (measured: 0 differing pixels between its own butt and round captures); this engine's is not, because it paints a stroke by filling the outline StrokeRec::apply_to_path returns and that outline is not cap-invariant at that width. Butt is byte-exact, so the other two caps are silently wrong pixels. No committed cell combines all three triggers and the tiger never declares a linecap, which is why five rungs of byte-exact bakes never showed it. The fix is its own rung — caps are inert per *contour*, so a path mixing open and closed contours cannot be normalized wholesale — so it is recorded with its reproduction rather than patched here.
`pnpm fmt:check` (oxfmt) failed on the three crate READMEs this branch touched. The change is table-column alignment only — `git diff -w` reports nothing but the separator rows, whose dashes lengthened to match. Fixes the node workflow's "Check formatting" step.
The additions-only gate compared the two lockfiles as text: any removed line failed. That encodes the right invariant for a crate cut — a refactor must not quietly change what the workspace depends on — but it cannot express a decision whose *subject* is dependency provenance. D-L pins Stylo at a revision; a branch carrying both a crate cut and that pin can never satisfy the rule as written, and the only outs were to bypass the gate or switch it off. Read the lockfiles as package graphs instead. A dropped package now fails unconditionally — something the textual check could not even name — and a withdrawn version fails unless docs/wg/consolidation/lock-graph-changes.toml enumerates that exact from/to move and names the decision behind it. Additions stay free, which is what the gate is called. The ledger opens with D-L's eighteen moves. Pinning the seven non-Stylo ones back was attempted first and refused by cargo: stylo_static_prefs 0.19.0 requires toml ^1.1.2, and stylo 0.19.0 requires indexmap ^2, which requires hashbrown ^0.16.1. No package is dropped and none renamed.
The gate used to be a line filter; it now decides whether a registered decision authorises a graph move, and a gate that cannot fail is not a gate. Seven controls, six of them negative: an unlisted bump fails, a dropped package fails even when the ledger names it, a missing ledger authorises nothing, and a row must match the version that landed rather than only the one withdrawn. The two positive cases pin the shape of a pass — an authorised move, and pure additions with no ledger at all. The gate reaches git through one function, so the controls substitute it and feed the decision logic hand-written lockfiles. No commits are invented and no Python test framework enters the tree.
D-L decided dependency provenance; this addendum measures what that decision does to Cargo.lock and why the additions-only gate had to learn about it. Eighteen versions move, no package is dropped, and none is renamed. Seven crates are members of the pinned tree, two are its direct registry requirements, and the rest follow from stylo_static_prefs requiring toml ^1.1.2 and stylo requiring indexmap ^2. Compliance was attempted first: all seven non-Stylo versions were offered back to cargo with --precise and all seven were refused, each with its forcing edge named. The bumps are the pin, not drift.
`patrol_stroke_width_units` documented three ingresses and implemented two. It
walks ancestors reading the presentation attribute and the `style` attribute,
so it never saw a `<style>` rule — and a sheet is not attributable to one
element without selector matching, which is why the leg it claimed could never
have lived there. Measured, the same declaration in three places:
stroke-width="2ex" refuses by name
style="stroke-width:2ex" refuses by name
<style>rect{stroke-width:2ex} renders, exit 0, at 16 units
That last row is silent wrong pixels, which this slice does not do.
The document-level scan that already walks every `<style>` element's text now
carries a second question alongside "does this declare a property the cascade
drops": "does it declare a stroke-width whose basis this build lacks". Same
unit list, same token test, so one rule covers all three spellings. Strict
refuses the document; best-effort declares once against the sheet and renders —
the same shape the neighbouring sheet finding already had, since neither can
name the elements a rule matches.
The patrol's docstring now describes the two legs it has, and points at the
document-level scan for the third. `DeclarationIgnored` gains "as authored": a
declaration can now depart by being misresolved, not only by being dropped.
Laws: the four spellings sit in one strokes-contract case, a best-effort case
pins what each admission does with each, and a control holds that the sheet leg
refuses the unit and not the word — `stroke-width: 8`, `0.5em` and `2px` still
render from a sheet, and a `1ex` belonging to another property in the same rule
is not this patrol's business. Both fail without the fix.
The cub found this at a second size and the register recorded it unfixed: a closed contour stroked at about one device pixel diverged from Chromium under a round or square cap, ~84 of 2304 pixels, while butt was byte-exact. Re-measuring moved every part of the diagnosis: - The register blamed filling `StrokeRec::apply_to_path`'s outline. A centred SVG stroke does not go that way — it sets `PaintStyle::Stroke` and hands Skia the cap, the same stroker Blink drives. - It is not the fill. A stroke-only closed contour diverges identically. - It tracks the *device* width, not the authored one: the same document at 2x diverges at an authored 0.5 and agrees at an authored 1. The threshold sits between 1 and 1.25 device pixels. - An *open* contour is byte-exact at every width and every cap — so this is not thin-stroke rasterisation in general, it is a cap appearing where the contour rejoins. SVG says a cap is inert on a closed contour, and Chromium agrees (its butt and round captures of one are byte-identical). So n0 normalises the cap to butt when every contour is closed. That is one draw, one composite pass, and byte-exact at every width probed. Except when a contour has no extent. `M44 32 Z` under a square cap is a dot Chromium paints, and SVG2 §13.2 makes the cap the only thing that renders it — normalising it away erased the dot, which the corpus caught. The predicate now declines to normalise anything that closes on the point it opened at, comparing on-curve endpoints only: a false positive merely keeps the authored cap, while a false negative would delete a dot. A path that *mixes* open and closed contours needs both caps at once, and one paint cannot carry both. Splitting the draw was measured and is worse — it fixes the thin case and then diverges by 32 to 47 pixels at 1.25 and 2 units, because the two runs' anti-aliased edges composite twice where they overlap. So websem refuses that combination by name instead. It over-refuses, since the error only appears at about a device pixel and the compiler cannot know the device; serving it properly means stroking each contour to an outline and unioning them, which is its own rung. svg-scene-cub is now byte-exact at 48x48 as well as 96x96.
Three cells at the combination no committed cell held: a closed contour, a non-butt cap, and a one-device-pixel stroke width. The existing cap cells stroke an open line sixteen units wide, and the closed-contour cell takes the default butt — between them they cover both halves of the trigger and never its intersection, which is why five rungs of byte-exact bakes said nothing and a scene at a second size had to find it. All three verify byte-identical to Chromium 149, so the stroke family is now 26 of 27 exact with only the declared conic tolerance remaining.
The addendum records the fix and, at equal length, what re-measuring overturned in the original diagnosis: the wrong consumer was blamed, the fill was incidental, the threshold is a device width rather than an authored one, and an open contour was byte-exact all along — so this was never thin-stroke rasterisation in general. Also recorded: the zero-length-dot exception the corpus caught within minutes, and why the mixed-contour case refuses rather than splitting the draw, with the measurement that ruled splitting out.
…me as CSS does Two silent-wrong-pixel holes, both in the fixes that claimed to close this class. **The cap normalisation covered one arm of three.** It landed in `ItemKind::PathStroke` only, so `<circle>` and `<ellipse>` — closed contours, cap-bearing, admitted — kept painting a cap Chromium does not. Measured against Chromium 149 on 48x48, differing pixels of 2304, per geometry and per cap: device width | closed path | oval | rect | line (open) 0.5 · 1 | 84–95 | 84–95 | 0 | 0 1.25 and up | 0 | 0 | 0 | 0 So this is per *arm*, not per closed contour: `draw_rect` never takes the thin-stroke path and needs nothing, `draw_oval` does. `LineStroke` keeps the authored cap — a line is open, and Chromium's own captures of one are not cap-invariant, which the probe confirms. `TextStroke` strokes closed glyph outlines but no admitted source reaches it, so it is named and left until text lands and can be measured. The law is now whole-geometry rather than per-element, because a per-element law is exactly what missed this: it iterates every closed geometry the slice admits, at the widths where the divergence lived, and asserts all three caps are pixel- identical. It fails without the fix. **A CSS property name is case-insensitive; one patrol leg was not.** `style="STROKE-WIDTH:1vw"` compiled to a stroke 12.8 units wide — the cascade's pinned 1280px device — where Chromium paints 0.64, with no refusal and no declaration. The guard was a case-sensitive `contains`. The sheet leg never had the bug, since it compares with `eq_ignore_ascii_case`; the docstring asserting the two legs together cover all three spellings was false until now.
Four cells: a circle and an ellipse, each under a round and a square cap, at a one-device-pixel width. With the three closed-path cells already committed that is seven cells over the intersection the corpus never held — a closed contour, a non-butt cap, and a width near one device pixel. The gap survived twice because the older cap cells stroke an *open* line sixteen units wide, and the closed-contour cell takes the default butt: each half of the trigger was covered and their intersection never was. Adding the path cells closed it for paths and left the ovals painting the same wrong pixels, which is what these four gate. The README's stroke tally and tolerance ceiling are corrected in the same commit, since both are statements about this manifest: 30 of 31 stroke cells byte-exact, and the corpus admits at most 6 differing pixels (svg-circle-viewbox-scaled, at delta 3) or at most delta 8 (svg-ellipse-fill, in 1 pixel) — never both at once, which the old "worst cell is 6 pixels at delta 3" read as a single bound.
The rewritten lock gate compared name and version. That is blind to substitution: this workspace tracks Stylo by `git+…?rev=…` with no checksum, so repointing that URL at a fork, or moving the rev, leaves the version untouched and passes as "0 dropped" — on precisely the axis the authorising decision is about, since the charter puts "a floating branch and private source fork" outside it. The comparison is now the whole coordinate: version, source and checksum. Three rules follow. A dropped package still fails unconditionally. A package re-published under an unchanged version *and* source — same coordinate, new checksum — now fails unconditionally too, because the bytes behind something immutable moved and no decision makes that legitimate. And a move that changes the source fails unless its ledger row spells out `from_source` and `to_source` verbatim. Running it against `main` found what the weaker comparison had been passing: eleven packages moved from crates.io to the pinned revision, and two of them — `servo_arc 0.4.3` and `to_shmem_derive 0.1.0` — did so at an unchanged version, so the old gate reported them as untouched. The ledger now records the registry-to-git move on every one, which is what the decision actually did. Five more controls, all negative: a fork swap with a silent ledger, with a row that omits the sources, with a row naming the wrong destination, a swap that passes only when both sources are declared, and a re-publish that fails even with a row. Twelve controls in total.
Seven statements this branch left behind its own work, each corrected against a measurement rather than a guess: - The SVG compiler's module doc still declared the pre-rung slice — solid-filled rect, circle and ellipse — three rungs after containers, paths, lines and strokes landed. It now enumerates the admitted surface, and its cascaded-patrol list names what `patrol_computed_style` actually reads (`display: contents`, `stroke-opacity` and `stroke-dasharray` were missing; shape `stroke` was listed and is now consumed). - The oracle gate's own doc claimed "if any RGBA pixel differs from Chromium" while six of the rows are checked against declared tolerances. It now says which six, why, and that the bounds are measured values rather than headroom. - The n0 README described the admitted slice as "rectangles"; it is rectangles, ellipses and paths, filled and stroked, with the closed-contour cap normalised. - The corpus README opened by calling itself a "first architecture prototype" and carried a superseded status note about a proving shell. It now says what the corpus is, and why the directory is named for a ratified amendment rather than a phase — `test-svg/` and `test-html/` hold the legacy renderer's corpora, which is the distinction the name carries. - The pages README claimed the engine "handles solid-fill SVG `<rect>` only". Rewritten to point at the shipped slice instead of restating a floor, so it cannot go stale again the next time that slice grows. - The vector-join spike's header said the leaf-vocabulary decision "still blocks" it. That decision was taken on 2026-07-23 on this very evidence; the header now says what it is — the independent witness behind a join already taken, with the production lowering named — and which leaves genuinely remain open. - The register reported a corpus count from three commits earlier and called the tiger "reproducible from that one command". The command lives in an untracked file, so the tiger is now stated plainly as a reported observation rather than a gate; the committed corpus is what reproduces from a clean checkout.
…ivate ids The two crates the SVG path rests on shipped no README at all. `websem` decides what this engine will and will not render, `rframe` is the contract it emits, and a reader arriving at either found only source. Both now have one, written at csscascade's scale rather than n0's: what the crate owns, the pipeline position, a module table, and an explicit **Anti-goals** list — websem is not a layout engine, not a text shaper, not a painter, not a second cascade, not a coverage claim; rframe is not an authored source, not a file format, not a renderer, not a second engine's contract. The admitted slice keeps exactly one home (`crates/n0_cli/README.md`) and the others point at it rather than copying it. Three of the five new crates were also missing from the maps a stranger actually reads. `AGENTS.md` gains rows for websem, rframe and animation-sampling — the table already described `n0_cli` as running "websem → rframe → n0" while defining neither — the documented check command gains all three, and the root README's workspace list gains animation-sampling. CLAUDE.md is a symlink to AGENTS.md and needs no edit. `rframe` also stopped exporting every type twice: `pub mod frame|path|stroke` became private, leaving the flat root every consumer already preferred, and the four deep imports collapsed with it. **The private vocabulary is out of the code.** Decision ids — D-C, D-L, D-M, D-N — are the register's own shorthand and belong in `docs/wg/consolidation/`, where the glossary defines them. Outside that tree they are tokens a stranger cannot resolve, and there were eighteen: four in `Cargo.toml` `description` fields, which is the worst case because those become published crates.io metadata, plus crate READMEs, rustdoc a reader sees via `cargo doc`, test module docs and assertion messages. Each now states the substance instead, and the four places where a link genuinely helps say what they link to rather than naming a ticket. "proving shell" went with them: it described a thing that no longer exists. Not done, deliberately: the `patrol_*` functions keep their name. The register uses "patrol" in exactly the code's sense at a dozen sites, so there is no collision to fix and renaming 29 identifiers would buy nothing.
… front page Thirteen of the twenty fixtures in `unsupported/` had no reader. The four that did were pinned one `include_str!` at a time, so a file could be added — or an old one could quietly start rendering — with nothing to notice. The corpus documented the slice's edge to humans and to nothing else. `unsupported_corpus.rs` reads the directory with `read_dir` and holds it against a declared table, so an undeclared file fails and a declared-but-missing one fails too. For each entry it asserts the departure names its construct in *both* admissions, and the split is itself the law: seven are document-level — the viewport grammar and root sizing decide the canvas, so best-effort cannot soften them into a hole without inventing pixels — and thirteen are attributable, so best-effort renders the rest and declares them by name at a structural path. There is no third outcome, which is the point: the property under gate is the programme's invariant stated over a whole directory rather than one construct, *nothing here renders silently*, and a fixture that started rendering would have nowhere to land. The register gains a front page in two places. `index.md` replaces a prose paragraph with a table of the seven papers, each with its genre, the decision it carries and whether that decision is settled — including that the conformance rule is unratified and no score may be computed. The engine-of-record paper gains a `Current state` section ahead of its eight dated addenda, so what is true at the tip is readable without reconstructing it from the log: the admission model, the admitted slice, the corpus counts, and what is not claimed.
`svg.rs` was 2675 lines with nothing in the tree to tell a reader that a full `preserveAspectRatio` implementation lives inside it. The obvious remedy — split it into patrol, transform, viewport, paint and shape siblings — is the wrong one, and the branch's own history says so: the strokes rung's 339-line diff touched the error type, an attribute table, the computed patrol, five shape compilers, the fill read and the stroke read, in one commit. Those are five of the six proposed files. Splitting would convert a one-file rung diff into a six-file scatter and leave ~40 private helpers needing `pub(crate)` in both directions — the shape of one algorithm, not six things. So only the grammar comes out. `svg_transform.rs` is a token list in and one affine out: no document, no cascade, no error type, no element, and — measured before moving it — zero outward calls except two shared string helpers. That is the same seam `svg_path.rs` already sits on. The rest is answered with a region map in the module header: entries and session, departures, shapes, paint, viewport, each naming its types, so the implementation is findable without being scattered. Three pre-existing rustdoc warnings about private intra-doc links went with it; `cargo doc -p websem` is now clean.
feat(consolidation): the n0 path becomes the SVG engine of record
Supersedes this PR's original scope. The Web-first prototype is the first third
of the branch; what follows it is the decision that prototype fed and the
capability the decision unlocked.
This is a decision-evidence PR, not a feature PR. 57% of the added lines are
evidence — 10,355 in laws, 5,432 in fixtures and Chromium oracles, 2,297 in
docs/wg— against 13,047 of crate source. That ratio is the point: two registrydecisions are taken here, and each carries the bar it had to clear.
What is decided
The n0 path (
websem → rframe → n0) is the SVG engine of record, static andanimated.
htmlcss::svgis demoted to a frozen semantics donor, minedper-feature. The product host routes every
.svg/.htmlrender through theretained compiler session — Base by default,
--time-nsfor one exactsigned-nanosecond sample. Alongside it, the kernel's join point is settled for
glyphless visual facts; shaped text stays a later stage.
Then five evolution rungs, each landed as a triple (engine + laws / corpus +
bake / docs) and each Chromium-baked:
WxHis the initial viewport, SVG2 §8.2) + the fullpreserveAspectRatiogrammar<circle>,<ellipse>, theauto-radius matrix, the zero-radius nothing<g>and the wholetransformgrammar, flattened rather than represented<path>, thedgrammar (no arc — see below),fill-rulestroke,stroke-width,-linecap,-linejoin,-miterlimit, and<line>What this does not claim
unratified (#49) and says no
score may be produced or inspected until it is. Nothing in this branch computes
one.
opacity, clips,gradients and patterns; none land here.
n0 --strictrenders ten rows offixtures/scoreboard/svg-rect-path-v0and refuses four, on exactly twoconstructs:
opacityandrxon<rect>. Those two are the whole distance tofull chassis coverage — and they are the next piece of work, not this one.
Evidence
except six curved cells carrying a declared, geometrically confined tolerance.
Provenance — browser version plus sha256 of the suite, sources, oracles and
bake script — is committed and re-verified by law.
svg-scene-cub: an original composition covering the Ghostscript tiger'sfeature list in 17 nodes, committed as a pair (animated plus its static Base
projection) so one scene gates the Base view and four exact-nanosecond
samples, at two sizes.
--strict, exit 0, zero declared degradations, bothadmissions byte-identical, 96.27% of pixels byte-identical to Chromium. It is
AGPL and stays untracked, so it is reported as an observation, not a gate —
the committed corpus is what reproduces from a clean checkout.
unsupported/is read fromdisk against a declared table: every fixture must depart by name in both
admissions — seven document-level refusals, thirteen attributable declarations,
no third outcome. The property is the invariant stated over a directory:
nothing there renders silently.
Deliberate divergences, each measured
Aas the ellipse'srational conics (measured byte-identical to
<ellipse>); Blink's cubicnormalizer, rendered by Chromium itself, differs from Chromium's own arc by 77
pixels at delta 170. The arc rung must emit conics, so the contract carries no
arc rather than a wrong lowering.
drefuses the whole path at its byte offset, where Chromiumrenders the valid prefix.
vw,ex, …) refuse rather than computeagainst the cascade's pinned device.
stroke-linecapon a path mixing open and closed contoursrefuses, because one paint cannot carry a cap per contour and both
alternatives are measurably wrong.
Three defects found and closed inside this PR
All three were silent wrong pixels — the one thing this programme does not
allow — and all three were in code this PR would merge.
A closed contour was stroked with a cap. Re-measuring moved the whole
diagnosis: not
StrokeRec::apply_to_path(a centred stroke uses Skia's nativestroker, the one Blink drives), not the fill (stroke-only diverges identically),
and it tracks device width rather than authored. The first fix covered
<path>only and left<circle>/<ellipse>diverging 84–95 px of 2304; themeasurement now spans every painter arm, and shows
draw_rectnever took thethin-stroke path while
<line>must keep its cap because Chromium's line capsare genuinely not invariant. Seven corpus cells cover the intersection.
A
<style>sheet could smuggle a basis-lessstroke-width, and so could acapitalised one.
stroke-width="2ex"refused;<style>rect{stroke-width:2ex}rendered at 16 units;
style="STROKE-WIDTH:1vw"rendered 12.8 units whereChromium paints 0.64. All three spellings now depart by name.
Seven capability claims had gone stale — the compiler's own module doc still
declared the pre-rung slice, the oracle gate claimed unconditional byte-exactness
while six rows carry tolerances. Each is corrected against a measurement.
One CI gate amended, deliberately and in the open
The
Cargo.lockadditions-only gate compared the two lockfiles as text. Thatis the right invariant for a crate cut but cannot express a decision whose
subject is dependency provenance — and this branch carries the Stylo pin.
Compliance was attempted before the rule was touched: all seven non-Stylo version
bumps were offered back to cargo with
--precise, and cargo refused all seven,naming the forcing edge each time. The gate now compares the whole coordinate —
version, source and checksum — and that immediately found what the old
comparison had been passing:
servo_arcandto_shmem_derivemoved fromcrates.io to the git pin at an unchanged version, which a name-and-version
check reports as no change at all. A dropped package and a same-coordinate
re-publish now fail unconditionally; a move that changes where a package comes
from fails unless the ledger spells both sources out. Twelve controls, ten of
them negative.
Gates
Run on this revision:
cargo fmt --all --check,cargo clippy --no-deps(0diagnostics),
pnpm fmt:check,cargo doc -p websem(0 warnings), the fullcargo test, the legacy pixel sweep (65/65 byte-identical tomain), thescoreboard v0 shape check, the seam-architecture locks, and the
Cargo.lockgate plus its own controls.
Reading order
69 commits, ordered, each rung a self-contained triple. Start with
docs/wg/consolidation/svg-engine-of-record.md— itsCurrent statesectionsays what is true at the tip without reconstructing it from the dated record
below. Then the rungs in commit order.