mirror of
https://github.com/luxfi/aml.git
synced 2026-09-24 00:25:20 +00:00
main
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e8e4ea81e4 |
fix(type): the console carries its own faces, because the CDN never had them
The shipped default fetched Zen from cdn.hanzo.ai. Nothing publishes it there: every /fonts/zen/* path answers 404, while /fonts/geist/1.7.2/ still answers 200 because those bytes were put there by hand. So every first paint of this console asked for two files that are not there and fell to the system stack — in a compliance product whose own tests assert it is set in Zen. The self-hosting path already existed as FONT_ORIGIN=, and it is now the default: vite emits the two faces out of @hanzo/font at the same paths, served same-origin. The CDN stays reachable through FONT_ORIGIN for the day the fleet publishes there, and the URL contract is still pinned so a rename cannot pass unnoticed. Three things follow, and each is a tightening rather than a trade: font-src drops its host. A cross-origin face has to be named in the policy; a face we carry does not, so the cheapest possible widening of a CSP is now no widening. csp.txt and the chart's copy move together, as they must. The cross-origin assertion gets stronger. It used to allow exactly one origin, ours; it now expects the set to be EMPTY — no subresource loaded from anywhere but here. An origin nothing fetches from cannot serve the wrong bytes, go down, or be asked for a beacon. And the pin moves to @hanzo/font 1.9.2. 1.9.0 and 1.9.1 shipped a zen.css whose :root block was missing, so the two custom properties a non-Next consumer reads were undefined — which computes to an invalid value and falls to the OS stack, silently. |
||
|
|
259ecc1411 |
fix(font): the console asked for Zen and was sent Geist
The kit moved to Zen — @hanzogui/font-zen names the families and @hanzo/font ships the bytes — but only half of this console followed. src/gui.ts took the stacks from the kit, so every rule asked for "Zen" and "Zen Mono", while src/font.ts still fetched Geist from /fonts/geist/1.7.2 and registered those bytes under the name the kit had just changed. A face installed under a name the file inside it does not carry is the failure this suite exists to catch, and font.spec.ts was left asserting Geist on both sides of it. Delivery now reads the same package the kit points at: the self-hosted build and the suite both take their bytes from @hanzo/font rather than from `geist`, and the licence emitted beside them is that package's, which carries Vercel's notice as well as ours because Zen is drawn from Geist. offline.spec.ts checks for both holders now, since dropping the upstream line is the same defect as dropping the licence. The CDN does not yet publish /fonts/zen/1.9.1. Until it does, ship this with FONT_ORIGIN= so the bundle serves the two files from the app's own origin. |
||
|
|
00bd8f8103 |
ui: the console is set in Geist, served from our own CDN
The serif is gone, but what replaced it was the machine's own sans — whatever
system-ui happens to resolve to, which is a different face on every desktop the
console is opened on. Geist Sans is the UI face and Geist Mono the monospace one
across every Hanzo property, so this console is set in them too, and fetches
them from cdn.hanzo.ai: one copy, one version, one cache entry shared with every
other property, and no request to a font host that is not ours.
The stacks are NOT written down here. They are read from @hanzo/gui's font
packages, because a stack copied into an app is a second source of truth that
does not fail when the kit's changes — it quietly stops matching the other
hundred properties. src/font.ts owns only DELIVERY: which release, which files,
which origin, and the @font-face rules. The face is registered under the family
its own stack leads with rather than under a name repeated beside it, so the
face installed and the family asked for cannot drift apart.
Three faults were found on the way and are each fixed and each pinned:
- The kit config had no `mono`, and ui.tsx asks it for `$mono`. A font token
the config does not define resolves to nothing.
- `.mono` lost to the kit. The kit dresses its own with
`.font_body, … { font-family: var(--f-family) }` — one class, in an adopted
stylesheet the cascade places after app.css — so a bare `.mono` tied on
specificity and lost on order, and machine text asked for through the kit
came out in the UI face: an id or a rate in proportional digits, in a
column, which is exactly where it shows. `:root :is(…)` settles it on
specificity, which no ordering can undo.
- The preload needed `crossorigin` even where the origin is our own. A font is
always fetched in CORS mode, and a preload that disagrees is a second
download rather than a saved one.
The policy gains one host in one directive — `font-src 'self'
https://cdn.hanzo.ai` — and is byte-identical otherwise. No unsafe-inline, no
unsafe-eval, no second directive, and the CDN may serve this console faces and
not code, style, or a place to send data. The faces go on through the CSSOM with
the kit's stylesheet, so style-src still sees no stylesheet parsed from markup.
An install that cannot reach us builds with `FONT_ORIGIN=` and serves the same
two files, at the same paths, from its own origin, with the OFL text beside
them — a build argument, not a fork. It is built and driven in the suite rather
than reasoned about, because an escape hatch nobody exercises is found broken by
a customer, on an install, with no network to debug it over.
Geist 1.7.2, Vercel and basement.studio, SIL Open Font License 1.1 — confirmed
against the licence shipped with the exact files hosted, which are the pinned
`geist` package's and not a copy of them.
Every assertion is mutation-proven: put back the serif-era selector, the old
policy, a drifted filename, proportional figures or a bare preload, and it is
the named test that fails. 22 green. Bundle +1,808 B; the two variable faces are
141 kB off-bundle, fetched once and cached for a year.
Co-authored-by: Hanzo Dev <[email protected]>
|
||
|
|
9f1ea316b9 |
ui: the console is set in a typeface a browser has
It shipped in Times New Roman. Two independent causes, both silent, both found by looking at the running console rather than by any check it passes: The kit's default config names its families `System` and `Heading`. Those are React Native names — that platform resolves them and this one does not — and the kit writes the name straight into `--f-family`, which every component reads. A family no browser knows is not an error; it takes the default, which is a serif. So every control, heading and cell the kit owns was serif. The families are now the web's own stack, given at createGui, so the kit emits it and nothing has to be overridden downstream. `--hz-font-sans` was declared in the theme bridge, which is on `.shell` and `.gate`. `body` is outside both, so `font-family: var(--hz-font-sans)` there substituted nothing, the declaration was invalid, and the document root fell back to the same serif. A typeface answers to no theme — one stack serves dark and light — so it belongs with the type scale at :root, which is where it is. The invariant is in the kit suite, and it is mutation-proven: put either cause back and it is the test that fails. It names the two stacks the console declares and refuses every other resolved family, so the next family that means nothing here fails the same way rather than needing its own case. 13 green. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
2a7a9175ca |
ui: the kit reaches the screens, and what it renders has been looked at
v0.3.4 put @hanzo/gui behind the shell's primitives and left the six pages on
raw HTML: 58 controls and 90 arrangement divs that the kit's Input, Button,
TextArea and stacks were sitting there unused for. The pages compiled against
gui-backed wrappers, so every check passed and the console still wore a
lookalike. They are converted now — zero <input>, <button>, <select> or
<textarea> outside ui.tsx, and layout is a value at the use site rather than a
class to look up in app.css.
ui.tsx is the screens' whole vocabulary and the only place an exception lives.
Four are kept, each because the kit's answer would be wrong: the four state
colours (a state must mean the same in a screenshot), the meter (geometry is an
SVG attribute, not a style), the three auto-fit grids (the kit's stacks are
flexbox and flex-wrap strands the last item), and one native Select (the kit's
needs a react-native pan responder this bundle does not carry).
Looking at it found three defects that a build, a typecheck, a CSP run and a
render test had all passed:
- `size` on a kit Button is a size TOKEN, so every label in the console was
rendering at its button's height — 24px type in a 24px box.
- Button does not forward className, so `tone` was dropped in silence: the one
action a screen exists for looked like every other action, and the segment
strips showed no selection at all.
- The theme bridge sat at :root. A custom property is substituted where it is
DECLARED, and the kit publishes themes as `:root .t_dark` — a descendant
selector that can never reach :root — so the bridge read the kit's
OS-preference default and handed every select a white ground and white ink.
Also drops the Button sub-theme placeholders (`--t0: dark`) on the way: they
are names, not colours, so tone reads the bridge instead.
Also found by looking: the panel body could not scroll, so a long case timeline
was unreachable, and the sign-in gate had lost its centring — both because CSS
addressed classes the components stopped rendering.
ui/e2e/kit.spec.ts is the guard, and it is a boundary AND a rendering check: it
reads these sources and fails on a raw control or a stylesheet arrangement in a
page, then measures a label, compares a primary button against a plain one and a
pressed segment against an unpressed one, and refuses any control that paints
its text in its own background colour. Written as invariants, not as three
regression cases, because the next one will not be the same bug. All six
mutation-proven: reintroduce the defect and the matching assertion fails.
12/12 browser tests green under the deployed CSP, signed in. The policy is
unchanged and still byte-identical to the chart. Go untouched: build, vet,
gofmt clean, 366 tests green under -race across 18 packages.
Co-authored-by: Hanzo Dev <[email protected]>
|
||
|
|
826ff1a773 |
ui: the console is the kit's, and the policy is proven rather than asserted
The console now renders through @hanzo/gui — the Hanzo design system — instead of a set of primitives written here over the raw tokens. Layout, surfaces, controls and type are the kit's. Two things deliberately are not: the four state colours, which must not change with a theme because a supervisor's copy of a screenshot has to mean what the screen meant, and the meter, which is drawn in SVG so a width that varies with data is an attribute rather than a style. The kit is imported at the part rather than at the barrel. @hanzo/gui's index re-exports every component it has, including ones built on react-native primitives this bundle never renders — Sheet's pan responder and the spinner's ActivityIndicator are the two that pulled a native runtime into a browser build. It is still one kit at one version. The whole CSP question is now in ui/src/gui.ts, stated once. GuiProvider would write the config's stylesheet as an inline <style>, which style-src governs and which would need 'unsafe-inline' or a hash that moves whenever the theme does. disableInjectCSS turns that off and the same CSS goes on through a constructed stylesheet — CSP does not govern the CSSOM, because the script that reached it had already passed script-src. The kit's per-component rules take the same route into empty <style> holders, which the SHA-256 of the empty string allows and which allows nothing else. ui/e2e is the part that was previously a claim. It builds the bundle, serves it under ui/csp.txt — byte-identical to the chart's HANZO_STATIC_CSP — seeds a session before the first script runs, and drives Chromium through every screen the rail offers. It carries a positive control: a style attribute parsed from markup is asserted to be REFUSED, because "no violations" means nothing without evidence that a violation would have been noticed. Writing it found two things a claim would not have: the kit's theme root does carry a style attribute (React writes it through element.style, so it applies and raises nothing — which is the distinction that matters and is now written down), and a stray server on the default preview port had been answering for a build nobody was testing. origin() is now apiOrigin(protocol, host), a pure function with a table for a test. It requires the first label to be `aml`, so evil.aml.hanzo.ai — a four-label host the old rule read as a domain to derive from, and one that resolves inside *.hanzo.ai — derives nothing. The certificate and the ingress host rule were the only things standing there before. @hanzo/iam is 0.21.5: logout ends the session (credentials + RFC 7009 revocation) and the callback error branch checks state before it reads the query string. The console-side proof of the second is in the harness. Co-authored-by: Hanzo Dev <[email protected]> |