Sky stdlib — Canonical correctness reference

Pairs with docs/rust-rewrite/ (the primary compiler reference; the Haskell-era docs/architecture/sky-compiler-architecture.md is historical context only). The compiler docs explain HOW Sky source becomes Go; this doc explains WHAT the stdlib surfaces guarantee.

This reference is grounded in the v0.17 HEAD sources at sky-stdlib/, the Rust kernel registry, and the Go runtime in runtime-go/rt/. Every claim either cites a file:line location, flags itself UNVERIFIED, or notes the regression spec that proves it.


1. Overview

1.1 What the stdlib is

Sky's stdlib is Layer-3: every kernel module is surfaced as Sky source under sky-stdlib/{Sky/Core,Std,Sky/Http}/*.sky (13,091 lines as of HEAD). Each binding is one of:

  1. Pure Sky — recursive / case-of / accumulator implementation. List.foldl, Maybe.map, Result.combine, every Std.Ui combinator.
  2. Ffi.kernel "Name" alias — Sky-source typed declaration whose call sites the compiler routes to the Haskell-side kernel dispatch, which then calls a Go runtime function in runtime-go/rt/. Zero runtime cost over a direct call; sky doc surfaces the entry.

The stdlib is the public contract. Anything not surfaced here is private compiler/runtime machinery and may change without notice.

1.2 Backends

BackendView renders asEffect runtime
Sky.LiveHTML over HTTP + SSE diffsGoroutine pool + session store
Sky.TuiANSI cells (rivo/uniseg)Goroutine pool + raw TTY
Sky.WebviewWKWebView (macOS v0.1)Same as Sky.Live, no HTTP
Sky.Clistdio / Std.LogGoroutine pool
Sky.Http.ServerPlain HTTP responsesGoroutine pool

All five share the same Element msg / Html msg / Cmd msg / Sub msg ADTs, so a view function paints identically across them (modulo <style>-driven primitives, which Sky.Tui silently ignores).

1.3 Compiler version

Compiler version this doc tracks: v0.16.6 release candidate (per CLAUDE.md current state) with the v0.17 typed-codegen close in progress on feat/v0.17-fully-typed-codegen. Every law and emission rule below holds against that compiler. Where v0.17 changes a surface, the change is called out inline.

1.4 Effect tiers (single rule)

Every observable side effect returns Task Error a.

flowchart LR
    Pure[Pure: bare a] --> Fallible[Fallible-pure: Result e a / Maybe a]
    Fallible --> Effects[Effects: Task Error a]
    Effects --> Diverging[Diverging: Int -> a]
TierExamples
PureString.length, List.map, Crypto.sha256, Time.timeString
Fallible-pureString.toInt, JSON decoders, Auth.hashPassword
EffectsFile.*, Http.*, Db.*, Time.now, Crypto.randomBytes
DivergingSystem.exit : Int -> a

System.getenvOr key default : String stays bare — the default plugs the failure case at the call site, no Task wrap needed.


2. Sky.Core algebraic primitives

2.1 Sky.Core.Maybe

File: sky-stdlib/Sky/Core/Maybe.sky (250 lines).

Type:

type Maybe a = Just a | Nothing

Surface (signatures verified at lines indicated):

FunctionSignatureFile:line
withDefaulta -> Maybe a -> aMaybe.sky:42
map(a -> b) -> Maybe a -> Maybe bMaybe.sky:52
andThen(a -> Maybe b) -> Maybe a -> Maybe bMaybe.sky:62
map2..5(a -> b -> ... -> z) -> Maybe a -> ... -> Maybe zMaybe.sky
andMapMaybe a -> Maybe (a -> b) -> Maybe bapplicative <*>
combineList (Maybe a) -> Maybe (List a)Maybe.sky:193
isJust/isNothingMaybe a -> BoolMaybe.sky

Mathematical claims (Functor / Applicative / Monad):

Partial functions: NONE. Every arm of every public function is total. withDefault plugs Nothing; map/andThen propagate.

Invariants:

Verification: by inspection of the case-arm structure. No dedicated property-test spec for the algebraic laws (UNVERIFIED by test). Maybe is small enough that the law-by-inspection bar is met.

Known gaps: None.


2.2 Sky.Core.Result

File: sky-stdlib/Sky/Core/Result.sky (228 lines).

Type:

type Result e a = Ok a | Err e

Surface:

FunctionSignatureFile:line
withDefaulta -> Result e a -> aResult.sky:27
map(a -> b) -> Result e a -> Result e bResult.sky:37
andThen(a -> Result e b) -> Result e a -> Result e bResult.sky:47
mapError(e -> e2) -> Result e a -> Result e2 aResult.sky
map2..5(a -> b -> ... -> z) -> Result e a -> ... -> Result e zResult.sky
andMapResult e a -> Result e (a -> b) -> Result e bapplicative <*>
combineList (Result e a) -> Result e (List a)Result.sky:199

Mathematical claims:

Result e is a Bifunctor (functor in both e via mapError and a via map) and a Monad in a (Err e short-circuits).

Partial functions: NONE.

Invariants:

Verification: by inspection. The CLAUDE.md non-regression rule "no Result String a in public surfaces" is enforced by the compiler test suite (cargo test, error-shape gates).

Known gaps: None.


2.3 Sky.Core.List

File: sky-stdlib/Sky/Core/List.sky (543 lines, the largest core algebraic module).

Type: builtin List a (compiler ADT, not Sky-source declared — the Sky type is [a] syntactic sugar over a cons cell).

Surface (key entries):

FunctionSignatureTCO classFile:line
map(a -> b) -> List a -> List bCPSList.sky:52
foldl(a -> b -> b) -> b -> List a -> bauto-TCOList.sky:138
foldr(a -> b -> b) -> b -> List a -> bCPSList.sky:160
filter(a -> Bool) -> List a -> List aCPSList.sky
concatList (List a) -> List aCPSList.sky
concatMap(a -> List b) -> List a -> List baccumulatorList.sky
take/dropInt -> List a -> List aCPS / TCOList.sky
appendList a -> List a -> List aCPSList.sky
lengthList a -> IntCPSList.sky
rangeInt -> Int -> List IntCPSList.sky
reverseList a -> List aauto-TCOvia reverseHelp
member/any/all/find(a -> Bool) -> List a -> Bool/Maybe aauto-TCOList.sky
zipList a -> List b -> List (a, b)CPSList.sky
indexedMap(Int -> a -> b) -> List a -> List bCPSList.sky

Mathematical claims:

Partial functions:

Invariants:

Verification: per-op spec under test/Sky/Build/CpsStackConstantBound/ (each verifies the rewritten body emits the auto-TCO marker — i.e. that the rewrite landed, not the law itself). Law-by-inspection for the algebraic content.

Known gaps:


2.4 Sky.Core.Dict and Sky.Core.Set

Files: Dict.sky (127 lines), Set.sky (92 lines). Both are Ffi.kernel aliases — implementations live in Go runtime (runtime-go/rt/rt.go — there are no dict.go / set.go files; the Dict_* / Set_* kernels live in rt.go and stdlib_extra.go).

Types:

type Dict k v  -- opaque; runtime is sync.Map for typed-key dicts,
               -- plain map[K]V for monomorphic specialisations
type Set a     -- opaque; sorted slice or hashset depending on a

Surface highlights:

FunctionSignature
Dict.emptyDict k v
Dict.insertk -> v -> Dict k v -> Dict k v
Dict.getk -> Dict k v -> Maybe v
Dict.removek -> Dict k v -> Dict k v
Dict.memberk -> Dict k v -> Bool
Dict.keysDict k v -> List k
Dict.toListDict k v -> List (k, v)
Dict.unionDict k v -> Dict k v -> Dict k v
Set.fromListList a -> Set a
Set.union/diff/intersectSet a -> Set a -> Set a

Mathematical claims:

Partial functions: NONE.

Invariants:

Verification: tests/ under stdlib exercises basic membership + union; examples/00-standard-libs exercises both modules in the 120-assertion stdlib smoke test.

Known gaps:


2.5 Sky.Core.String

File: String.sky (218 lines). 38 entries. All Ffi.kernel aliases backed by Go runtime functions that respect Unicode where the documentation says they do.

Key signatures:

length         : String -> Int                    -- rune count, not byte count
reverse        : String -> String                 -- rune-aware
toInt          : String -> Maybe Int
fromInt        : Int -> String
toFloat        : String -> Maybe Float
fromFloat      : Float -> String
toUpper        : String -> String                 -- Unicode-aware
toLower        : String -> String
trim/trimStart/trimEnd : String -> String          -- trims Unicode whitespace
contains/startsWith/endsWith : String -> String -> Bool  -- needle-first
containsIn/startsWithIn/endsWithIn : String -> String -> Bool  -- haystack-first (v0.15.47+)
slice          : Int -> Int -> String -> String   -- rune indices, Elm semantics
dropLeft/dropRight : Int -> String -> String       -- rune-based (v0.16.31)
casefold/equalFold : ...                            -- Unicode case-folded comparison
isEmail/isUrl   : String -> Bool                    -- format checks
words/lines    : String -> List String

Mathematical claims:

Partial functions: NONE. toInt / toFloat return Maybe. slice clamps out-of-bounds indices.

Invariants:

Verification: stdlib smoke test (examples/00) hits ~30 of 38 entries. The compiler test suite's UI-layout specs don't cover String, but the tests/Sky.Core.String* suite covers padding, trim, and contains.

Known gaps:


2.6 Sky.Core.Math (36 entries)

File: Math.sky (206 lines).

Surface: abs, min, max, sqrt, pow, cbrt, hypot, exp, exp2, log, log2, log10, floor, ceil, round, trunc, sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh, mod, remainder, plus constants pi, e, phi, sqrt2, inf, nan.

Mathematical claims:

Verification: examples/00-standard-libs smoke. The parity-with-Go is by-inspection of the runtime binding (one-line delegates).

Known gaps:


2.7 Sky.Core.Crypto

File: Crypto.sky (158 lines).

Surface:

FunctionSignatureTier
sha256/sha512/sha1/md5String -> Stringpure
hmacSha256/hmacSha512String -> String -> Stringpure
rsaSha256Sign/VerifyString -> String -> ...Result Error _
constantTimeEqualString -> String -> Boolpure
aesGcmEncrypt/DecryptString -> String -> ... -> Result Error Stringfallible-pure
chacha20Encrypt/Decrypt...fallible-pure
aesKeyFromPassword/chachaKeyFromPasswordString -> Int -> Result Error Stringfallible-pure (PBKDF2)
randomBytesInt -> Task Error Stringeffect
randomTokenInt -> Task Error Stringeffect

Security claims:

Verification: runtime-go/rt/crypto_test.go covers the AEAD round-trips, HMAC parity vs. test vectors, and the constant-time-equal contract for select cases.

Known gaps:


2.8 Sky.Core.Jwt

File: Jwt.sky (297 lines).

Surface:

type Algorithm = HS256 String | RS256 String   -- secret / privateKey
type Claims                                     -- opaque builder

encode : Algorithm -> Claims -> Result Error String
decode : Algorithm -> Int -> String -> Result Error Claims
  -- second arg is `now` in unix seconds; checks exp + nbf

claims : Claims  -- empty
issuer / subject / audience / expiresAt / notBefore / issuedAt
 / jwtId / withClaim : ... -> Claims -> Claims  -- builders

Security claims:

Verification: runtime-go/rt/jwt_test.go covers round-trip, exp/nbf rejection, signature mismatch, alg-substitution attempt. Issue #555 fix (typed-Dict claims round-trip via gob) lives in Auth_signToken runtime — claims are no longer dropped at signing.

Known gaps:


2.9 Sky.Core.Task (concurrency + effects)

File: Task.sky (305 lines).

Type: Task e a — opaque; runtime is a func() (Result[e,a], error) thunk wrapped in a typed dispatch (since v0.15.46, all Task callbacks are typed SkyTask[E, T] so the kernel knows the exact runtime shape).

Surface:

FunctionSignature
succeeda -> Task e a
faile -> Task e a
map(a -> b) -> Task e a -> Task e b
andThen(a -> Task e b) -> Task e a -> Task e b
mapError(e -> e2) -> Task e a -> Task e2 a
onError(e -> Task e2 a) -> Task e a -> Task e2 a
fromResultResult e a -> Task e a
andThenResult(a -> Result e b) -> Task e a -> Task e b
sequenceList (Task e a) -> Task e (List a)
parallelList (Task e a) -> Task e (List a)
performTask e a -> (Result e a -> msg) -> Cmd msg
runTask e a -> Result e a (synchronous main-only)
lazy(() -> Task e a) -> Task e a
retryWithRetryPolicy e -> Task e a -> Task e a

Mathematical claims:

Effect-boundary law: every Task at the top of main MUST be forced — either by explicit Task.run (CLI/Cli) or by the runtime entry point (Live.app, Server.listen). A let _ = TaskExpr inside Sky source is auto-forced by the compiler via rt.AnyTaskRun. This is structural — the compiler emits the wrap; the user does not opt in.

Synchronous panic gate (v0.15.43): every emitted func main() starts with defer rt.LogPanicAndExit(). A panic in synchronous Sky (Sky.Cli / batch / non-server main) is recovered, classified (DivisionByZero / TypeMismatch / CoerceFailure / etc.), logged with a 4-byte errId, and the process exits 1. Server handlers have a per-request defer/recover. The Cmd.perform goroutine wraps rt.SafeGo.

Verification: runtime tests in runtime-go/rt/task_test.go, retry_test.go. Examples 07-todo-cli and 18-job-queue exercise the two-level error pattern (correlation ID + structured log + user message).

Known gaps:


2.10 Other Sky.Core modules (terse)

ModuleLinesPurposeCorrectness verdict
Basics49identity, always, not, clamp, modBy, comparePure, by-inspection
Bytes112empty/length/fromString/toString/fromHex/toHex/append/sliceRound-trips by-inspection
Char57isAlpha/isDigit/isLower/isUpper/toUpper/toLower/toCode (v0.16.7 #419)Unicode-aware
Encoding47base64/urlEncode/hexEncode + inversesRound-trip pairs
Path27base/dir/ext/isAbsoluteOS-aware via filepath
Process17run subprocessEffect; see Task tier
Regex42match/find/findAll/replace/splitRE2 backed (Go) — no catastrophic backtracking
Random144int/float/range/choice/shuffle/weighted; seed variantsEntropy: crypto/rand for Task tier; splitmix64 for seeded tier
Uuid38v4 (random) / v7 (timestamp-ordered) / parseRFC 4122 compliant
Http142get/post/request + parseQuery; HttpResponse typed recordEffect; see Task tier; TLS via Go net/http
WebSocket297client + server bidirectional socketsBuilt on nhooyr.io/websocket; production gate refuses empty originPatterns when ENV=production
Time76now/sleep/every/unixMillis/format*/timeStringUTC by default; IANA zones via Std.Time
ToString54fromInt/fromFloat/fromBool/fromTimeNaming-consistency aliases; zero runtime cost
Pure143uniform () -> Task Error a companion surface (v0.15.50+)Tail-call aliases; HM-portable

Algebraic correctness for these is uncontroversial. Effect-tier modules inherit Task's contract (failure surfaced; never panics in well-typed code).


3. Std.Ui layout DSL

This is the section the user flagged as "particularly UI libs, so we're certain what goes what and are things mathematically correct".

3.1 Core types

-- sky-stdlib/Std/Ui.sky:54
type Element msg
    = ElText String
    | ElNode LayoutContext (List (Attribute msg)) (List (Element msg))
    | ElLink (List (Attribute msg)) { url : String, label : Element msg }
    | ElImage (List (Attribute msg)) { src : String, description : String }
    | ElButton (List (Attribute msg)) { onPress : Maybe msg, label : Element msg }
    -- ... (full ADT)

-- sky-stdlib/Std/Ui.sky:70
type Attribute msg
    = AttrWidth Length
    | AttrHeight Length
    | AttrPadding Int Int Int Int      -- top right bottom left
    | AttrSpacing Int
    | AttrAlign HAlign VAlign
    | AttrEvent (Event msg)
    | AttrStyle String String          -- CSS prop, value
    | AttrAttribute String String       -- raw HTML attr (data-*, aria-*, ...)
    -- ... (full ADT)

type Length
    = Px Int | Fill Int | Content | Min Int Length | Max Int Length
    | Vh Int | Vw Int

Element msg and Attribute msg are PARAMETRIC on the user's msg type, so event-emitting elements carry their typed handlers all the way to the runtime dispatch. This is the foundation that closes the "untyped event handler" class.

3.2 Layout combinators

CombinatorCSS emittedFile:line
el attrs childdisplay: flex; flex-direction: column; (single child)Ui.sky:219
row attrs csdisplay: flex; flex-direction: row;Ui.sky:224
column attrs csdisplay: flex; flex-direction: column;Ui.sky:229
wrappedRowdisplay: flex; flex-direction: row; flex-wrap: wrap;Ui.sky
griddisplay: grid; grid-template-columns: ...;Ui.sky
gridColumns Ngrid-template-columns: repeat(auto-fill, minmax(Npx, 1fr));Ui.sky
layout attrs el100vh page wrapper + flex column rootUi.sky:1697
layoutWith cfg eladditive: wrapperAttrs reach outer 100vh <div>, rootAttrs apply to rootUi.sky

3.3 The Ui.fill asymmetry (load-bearing invariant)

This is the most-important Std.Ui invariant. Verified at the source level in sky-stdlib/Std/Ui.sky and gated by test/Sky/Build/UiFillCssSpec.hs.

Type:

-- sky-stdlib/Std/Ui.sky:415
fill : Length
fill = Fill 1

fillPortion : Int -> Length
fillPortion n = Fill n

Emission contract (v0.15.55, refined v0.15.56):

PositionCSS emitted
Main-axis fillflex-grow: N; min-{w,h}: 0;
Cross-axis HEIGHT fill (row child)(nothing — relies on flex default align-items: stretch)
Cross-axis WIDTH fill (column / el / textColumn child)width: 100%;
flowchart TD
    Parent[Parent layout context] --> Decide{Child fills which axis?}
    Decide -->|Main axis| Grow["emit: flex-grow: N; min-w/h: 0"]
    Decide -->|Cross HEIGHT in row| Nothing["emit: nothing (default stretch)"]
    Decide -->|Cross WIDTH in column| Width["emit: width: 100%"]

Why the asymmetry: CSS Flexbox §9.8 resolves % against a parent's USED size only when "definite". A flex-grow-derived height is INDEFINITE. Row parents commonly have indefinite heights → the pre-v0.15.55 height: 100% on cross-axis fill collapsed every child to text-content height (issue #63 — three-pane app shell, Input.multiline → 22/51 px). Width keeps 100% because column-parent widths are typically definite AND it survives the [Ui.width fill, Ui.centerX] cascade.

Mathematical claim: Ui.fill is the maximum-monotone element of the Length lattice with respect to the parent's available space. For a row of two Ui.fill children, each gets 50% main-axis. For [Ui.fillPortion 1, Ui.fillPortion 2] the second gets 2/3.

Verification:

3.4 The align-self single-emission invariant

v0.15.56 F4 contract (file Ui.sky):

Test: test/Sky/Build/UiAlignSelfSpec.hs (currently modified per git status — same caveat).

Mathematical claim: rendering is ORDER-INDEPENDENT — swapping the order of two alignment attributes in [Ui.centerX, Ui.alignTop] produces byte-identical output.

3.5 Void-element pseudo-class hoist (v0.15.57 #409)

Problem: pre-v0.15.57, the runtime prepended sky-id-scoped <style> blocks as the FIRST CHILD of the carrying element. Fine for <div> / <button>. SILENTLY DROPPED on void HTML elements (<input>, <img>, <br>, <hr>, ...) — renderVNode skips children for void tags.

Post-v0.15.57: the style block is hoisted to a SIBLING slot immediately after the void element. CSS selector still keys off the void element's sky-id, so the rule applies correctly.

Implication: Input.text [Background.activeColor (...), ...] cfg now correctly applies :active / :hover colours to the <input>.

Verification: a compiler-test spec exists under Sky.Build.UiPseudoClassHoist*Spec (UNVERIFIED at this read — file exists per CLAUDE.md reference; line counts not checked).

3.6 Media-query auto-wrapping

Hover gate — every :hover rule emitted via Background.hoverColor / Font.hoverColor / Border.hoverColor / etc. is AUTO-WRAPPED in @media (hover: hover) by the runtime. This closes the classic mobile "tap-and-stay-hovered" bug.

Reduced-motion gate — every CSS transition (Std.Ui.Transition) and keyframe animation (Std.Ui.Animation) is AUTO-WRAPPED in @media (prefers-reduced-motion: no-preference) by default. Opt out via Transition.attributeUnsafe or respectReducedMotion = False on the Animation Spec ONLY when motion is semantically required.

Implementation at Std/Ui.sky:1183:

"(prefers-reduced-motion: reduce)"   -- runtime checks; spec sees no-preference

Mathematical claim: for any element with hover state H and non-hover state N, on a touch device the runtime presents N (not H stuck-after-tap). For any reduced-motion-preferring user, the runtime presents the keyframe-final state, NOT the animated transition.

3.7 Pseudo-class / transition / animation rule storage

Three parallel mechanisms, identical pattern:

SurfaceMarker attrWhere the rule is emitted
Pseudo-classesdata-sky-pc-rulesUi.sky:1936 (parsed by runtime into <style data-sky-pc="<sid>">)
Transitionsdata-sky-tr-rules + data-sky-tr-respectUi.sky:1949 (<style data-sky-tr=...>)
Animationsdata-sky-anim-rulesUi.sky:1977 (<style data-sky-anim=...>)
Media queriesdata-sky-mq-q + data-sky-mq-rulesUi.sky:1244 (<style data-sky-mq=...>)

Each element gets a sky-id; the runtime expands the marker attr into a sibling <style> whose CSS selector keys off the sky-id. Two elements naming the same animation "fadeIn" with different keyframes don't collide globally because the runtime auto-suffixes the @keyframes name with the element's sky-id-derived ident (fadeIn__r_1_div_0).

3.8 Std.Ui surface catalogue

flowchart TB
    Element[Element msg ADT]
    Element --> Layout[Layout: el / row / column / wrappedRow / grid / paragraph / textColumn]
    Element --> Sized[Sized: link / image / button / input / form]
    Element --> Text[Text: text / none]
    Element --> Raw[Escape: html]

    Attr[Attribute msg ADT]
    Attr --> Sizing[Sizing: width / height / Length: px/fill/content/vh/vw/minimum/maximum]
    Attr --> Padding[Padding: padding / paddingXY / paddingEach / spacing]
    Attr --> Align[Alignment: centerX/Y / alignLeft/Right/Top/Bottom / pointer]
    Attr --> Overflow[Overflow: clip / clipX/Y / scrollbars]
    Attr --> Nearby[Nearby: above/below/onLeft/onRight/inFront/behind]
    Attr --> Event[Events: onClick / onSubmit / onInput / onFocus / onKeyDown / ...]
    Attr --> Style[Style sub-modules]

    Style --> Background[Background: color / image / linearGradient / hover/focus/active/disabled variants]
    Style --> Border[Border: color / width / rounded / solid/dashed/dotted / shadow / inner / hover variants]
    Style --> Font[Font: color / family / size / weight / italic / underline / letterSpacing / hover/focus variants]
    Style --> Region[Region: semantic landmarks → h1..h6, main, nav, aside, footer, aria-*]
    Style --> Input[Input: button/text/multiline/email/password/checkbox/radio/slider]
    Style --> Lazy[Lazy: LRU-cached subtrees]
    Style --> Keyed[Keyed: sky-key for diff identity]
    Style --> Responsive[Responsive: classifyDevice / adapt]
    Style --> Transition[Transition: typed CSS transitions w/ reduced-motion gate]
    Style --> Animation[Animation: keyframe specs w/ reduced-motion gate]
    Style --> Transform[Transform: typed transform property helpers]
    Style --> Grid[Grid: Track ADT (fr/px/auto/minContent/minmax/repeat/repeatAutoFit/repeatAutoFill)]

3.9 Std.Ui correctness verdict

PropertyStatus
ADT exhaustivenessVerified by exhaustiveness checker in compiler
fill asymmetric emissionSpecified at Ui.sky:415 + verified by UiFillCssSpec
align-self single-emissionSpecified F4 contract + verified by UiAlignSelfSpec
Void-element pseudo-class hoistShipped v0.15.57 (#409)
:hover auto-wrapped in @media (hover: hover)Documented + implemented
Transitions/animations auto-wrapped in @media (prefers-reduced-motion: no-preference)Documented + implemented
Sky.Ui → Sky.Tui parity~95% of primitives; gradients/letter-spacing/image-fills emit deduped tuiWarn
Sky.Ui → Sky.Webview paritySame renderer as Sky.Live (WKWebView; macOS only in v0.1)
No raw HTML / no data-sky-evalEnforced — Std.Html escapes everything; eval is forbidden

3.10 Std.Ui known gaps


4. Std.Html + Sky.Live TEA architecture

4.1 Std.Html ADT

File: sky-stdlib/Std/Html.sky:26.

type Html msg
    = HElement String (List (Attribute msg)) (List (Html msg))
    | HText String
    | HRaw String     -- trusted pre-rendered content only

Contract:

Compositional law: text / node / voidNode form the smallest generators. Every other helper (div, span, p, ...) is node "<tag>" partial-applied. So div [] [text "x"] is structurally identical to node "div" [] [text "x"].

4.2 TEA shape (Live.app cfg)

main =
    Live.app
        { init = init                  -- Request -> (Model, Cmd Msg)
        , update = update              -- Msg -> Model -> (Model, Cmd Msg)
        , view = view                  -- Model -> Element Msg (or Html Msg)
        , subscriptions = subscriptions -- Model -> Sub Msg
        , routes = [...]               -- URL routing
        , notFound = HomePage          -- fallback
        , head = headFor               -- OPTIONAL Model -> List (Html Msg)
        , consoleAuth = ...            -- OPTIONAL row-poly
        , status = ...                 -- OPTIONAL i18n
        }

The cfg is row-poly (extensible records). Apps that omit optional fields build byte-identical to the pre-extension shape.

4.3 init lifecycle

flowchart LR
    Req[HTTP request hits /] --> Cookie{sky_sid cookie present?}
    Cookie -->|No| Init["init req called → Model + Cmd Msg"]
    Cookie -->|Yes| Resume["Restore Model from session store; init does NOT run"]
    Init --> Render[Render view → full HTML]
    Resume --> Render
    Render --> SSE[Open SSE channel for patches]

Critical contract: init is per-SESSION, not per-page-reload. Browser reload while session alive → resume from store. Force fresh init via Cmd.perform (Cookie.expire "sky_sid") then reload.

req shape (v0.16.7 #417 + v0.16.8 #423):

FieldTypeNotes
req.pathStringURL path
req.queryStringRaw query string (use Sky.Core.Http.parseQuery)
req.paramsDict String StringMatched :name route segments
req.methodString"GET" / "POST" / ...
req.headersDict String StringCanonical-case
req.cookiesDict String StringParsed

Adding ANY of these fields to a user req access pattern is row-poly safe — pre-v0.16.8 apps that read req.path continue to build.

4.4 Cmd / Sub algebra

Std.Cmd (sky-stdlib/Std/Cmd.sky:36-83):

type Cmd msg                                   -- opaque

none      : Cmd msg                             -- identity
batch     : List (Cmd msg) -> Cmd msg           -- associative + commutative monoid w/ none
perform   : Task e a -> (Result e a -> msg) -> Cmd msg
publish   : String -> any -> Cmd msg            -- pub/sub (echo by default)
publishNoEcho : String -> any -> Cmd msg        -- pub/sub (skip publisher's subscription)

Std.Sub (sky-stdlib/Std/Sub.sky:14-51):

type Sub msg                                    -- opaque

none           : Sub msg
every          : Int -> msg -> Sub msg          -- tick every N ms
batch          : List (Sub msg) -> Sub msg
subscribeTopic : String -> (any -> msg) -> Sub msg

Monoid laws on Cmd.batch and Sub.batch:

4.5 SSE patch protocol

sequenceDiagram
    participant Browser
    participant Server as Sky.Live runtime
    participant Update as user update fn

    Browser->>Server: GET / (full HTML)
    Server-->>Browser: HTML + open SSE channel /_sky/sse
    Server->>Server: heartbeat every 15s
    Note over Server: hello handshake with seq=0
    Browser->>Server: POST /_sky/event (typed Msg payload)
    Server->>Update: dispatch Msg, get (Model', Cmd)
    Server->>Server: diff(prevVNode, view Model')
    Server-->>Browser: SSE event "patch" with VNode patch JSON
    Server->>Server: execute Cmd in goroutine
    Note over Server,Update: If Cmd.perform completes → new Msg → another diff

Hardening rules (documented in CLAUDE.md "Reverse-proxy hardening"):

XSS hardening (#338 / C9): __skyReviveScripts allowlist drops event handler attributes (onclick, onerror, ...). Re-injection of scripts post-patch goes through this gate.

Input preservation (3 failure modes closed):

  1. Empty patches JSON-ack (don't HTML-fallback) → preserves uncontrolled fields like password.
  2. Full-body swap preserves EVERY uncontrolled INPUT/TEXTAREA/SELECT (not just document.activeElement).
  3. Open <select> defence — __skyApplyPatches skips patches targeting focused/contained selects.

4.6 URL routing + history

4.7 Wire-event arg shapes (mandatory contract)

EventElement typeArgs
click/focus/blur/mouseover/outany[]
input/changecheckbox[checked : Bool]
input/changeradio[checked : Bool] — use onClick per choice instead
input/changenumber / range[value : Float]
input/changetext / textarea / select[value : String]
submitform[formData]Dict String String OR typed record
keydown/keyup/keypressany[key : String]

4.8 Password forms — mandatory pattern

Rule: Use onSubmit with form data; NEVER onInput per keystroke on password fields.

type alias AuthCreds = { email : String, password : String }
type Msg = UpdateEmail String | DoSignIn AuthCreds

view model =
    form [ onSubmit DoSignIn ]
        [ input [ type "email", name "email", value model.email, onInput UpdateEmail ] []
        , input [ type "password", name "password" ] []   -- no value, no onInput
        , button [ type "submit" ] [ text "Sign in" ]
        ]

Three reasons (CLAUDE.md):

  1. Password managers watch DOM mutations on password inputs; every server-driven re-render with value= triggers re-prompt.
  2. Secret never lives in Model → never serialised into session store.
  3. Form submit reads live DOM value, not debounced keystrokes.

The DoSignIn AuthCreds constructor takes a typed record. The wire driver decodes form data directly into the Go struct via case-insensitive json.Unmarshal. No per-Msg decoder boilerplate.

4.9 Sky.Live correctness verdict

PropertyStatus
TEA shape (init/update/view/subs) is totalVerified by HM type-checker
Cmd/Sub none/batch monoidHeld by inspection; compiler-test specs cover batch
SSE patch idempotence__skyApplyPatches is idempotent on no-op patches
Input preservation across re-rendersClosed via 3 documented mechanisms (C1 residuals)
XSS-resistant __skyReviveScriptsAllowlist + event-handler stripping (#338)
Session restore on browser reloadVerified — init does NOT run on resume
URL routing — literal before patternDocumented; matching is by-declaration-order
Password-form anti-pattern enforcementDocumented; not statically checked (UNVERIFIED)
Production gate (ENV != dev) closes console/banner/metricsImplemented in productionFromEnv(); v0.15.43 panic gate

4.10 Sky.Live known gaps


5. Std.Db + Std.Auth

5.1 Std.Db typed parameter binding (v0.16.26)

File: sky-stdlib/Std/Db.sky (551 lines).

Type ADT at Db.sky:349-358:

type SqlValue
    = SqlString String   -- TEXT / VARCHAR / CHAR / UUID-as-text / JSON-as-text
    | SqlInt Int         -- INTEGER / SMALLINT / BIGINT / SERIAL
    | SqlFloat Float
    | SqlBool Bool
    | SqlBytes String    -- BLOB / BYTEA (raw bytes as Sky String)
    | SqlDecimal Decimal -- arbitrary-precision NUMERIC / DECIMAL
    | SqlTime Time       -- TIMESTAMP / TIMESTAMPTZ
    | SqlMoney Money     -- TEXT "ISO_CODE AMOUNT" (paired with Db.Decode.money)
    | SqlNull SqlValue   -- typed NULL — wrapped variant is the type-witness

Nullability invariant: SqlNull (SqlInt 0) means "NULL, Int type". The wrapped value is the WITNESS — its Sky value is discarded; only the driver needs the type tag.

fromMaybe* helpers at Db.sky:367+:

fromMaybeString : Maybe String -> SqlValue
fromMaybeString m =
    case m of
        Just v  -> SqlString v
        Nothing -> SqlNull (SqlString "")

8 variants cover String / Int / Float / Bool / Bytes / Decimal / Time / Money.

SqlField ADT (PATCH semantics):

type SqlField = SetField SqlValue | OmitField

Used by Db.updateFields conn table whereCols setFields to generate dynamic UPDATE that includes only SetField columns. Identifier allowlist rejects characters outside [A-Za-z0-9_.].

5.2 Std.Db security invariants

5.3 Std.Db migrations

Db.migrate (Db.sky) implements versioned forward-only schema migrations:

5.4 Std.Auth contract

File: sky-stdlib/Std/Auth.sky (122 lines).

register : Db -> String -> String -> Task Error Int       -- email, password
login    : Db -> String -> String -> Task Error Int
setRole  : Db -> Int -> String -> Task Error ()

hashPassword     : String -> Result Error String          -- bcrypt cost 10
hashPasswordCost : String -> Int -> Result Error String   -- bcrypt cost N
verifyPassword   : String -> String -> Result Error Bool
passwordStrength : String -> Result Error Int

signToken           : String -> a -> Int -> Result Error String
verifyToken         : String -> String -> Result Error a
signTokenWithClaims     : Jwt.Algorithm -> Jwt.Claims -> Result Error String
verifyTokenWithAlgorithm : Jwt.Algorithm -> Int -> String -> Result Error String

Security invariants:

Verification:

5.5 Std.Db / Std.Auth verdict

PropertyStatus
SqlValue covers full driver type range9 variants (closed v0.16.26)
Identifier allowlist on dynamic SQLSpecified + enforced
Migration checksum gateImplemented in migrate
signToken secret is typed StringEnforced; v0.15.44+ contract
Bcrypt password hashingDefault cost 10; adjustable
Tenant isolation in console storev0.16.6 SQL-layer enforcement
Argon2id / X25519 / Ed25519NOT shipped — documented gap
JWE (encrypted JWT)NOT shipped — only JWS
Migration rollbackNOT supported (forward-only by design)

6. Cross-backend parity

flowchart LR
    SkySrc[Same view function: Model -> Element Msg]
    SkySrc --> Live[Sky.Live: WebKit/Chromium]
    SkySrc --> Tui[Sky.Tui: ANSI cells]
    SkySrc --> Webview[Sky.Webview: WKWebView]

    Live -->|HTTP + SSE| LiveR[liveAppRun + session store]
    Tui -->|Goroutine + raw TTY| TuiR[Tui runtime + uniseg]
    Webview -->|In-process Bind/Eval| WvR[webview_go]
FeatureSky.LiveSky.TuiSky.Webview
Std.Ui layout primitivesFull~95%Full (mirrors Live)
Background gradients / image fillswarn
Font letter-spacingwarn
Pseudo-classes (:hover / :focus)warn
Transitions / animationswarn
Media queriesignored
Std.Live.Head per-page <head> injectionn/a✓ (head ignored — single shell window)
URL routing + historyn/a✓ (in-process navigation only)
SSE diff patches✓ (cell diff)✓ (in-process)
Session store (memory/sqlite/redis/...)n/an/a (single process)
Cmd.perform
Cmd.publish / Sub.subscribeTopic
Production gate (ENV != dev)n/an/a
TTY signal teardown (SIGTERM/SIGHUP/SIGQUIT/SIGINT)n/a✓ (WKWebView lifecycle)
Backend OS supportAny (Linux/macOS/Win Go)Any TTYmacOS only in v0.1
cgo requiredNoNoYes (sky build auto-detects rt.Webview_app)

Identical view function — the same Model -> Element Msg produces three byte-identical paint results modulo style primitives that one backend does not implement (Sky.Tui silently ignores <style>; Sky.Webview honours media queries identically to Sky.Live).


7. Correctness verdict

7.1 Per-module verdict

ModuleMathematicalStructuralVerified byVerdict
Sky.Core.MaybeFunctor/App/Monad laws hold by inspectionn/aInspection + sweepSOLID
Sky.Core.ResultFunctor/Bifunctor/Monad laws holdn/aInspection + sweepSOLID
Sky.Core.ListFunctor + fold laws + constant-stack contractn/aPer-op CPS specSOLID (v0.17 close)
Sky.Core.Dict/SetFinite map/set lawsStable iterationSweepSOLID
Sky.Core.StringRune-aware round-tripsn/aSmoke testSOLID-mostly (grapheme gap)
Sky.Core.MathIEEE 754 via Go mathn/aInspectionSOLID
Sky.Core.CryptoAEAD + constant-time-equaln/art/crypto_test.goSOLID-mostly (no Argon2id/Ed25519)
Sky.Core.JwtSignature-then-claims + exp/nbfalg: none rejected by ADTrt/jwt_test.goSOLID
Sky.Core.TaskMonad laws + effect tier disciplinePanic gate v0.15.43rt/task_test.go + retry_test.goSOLID
Std.Uin/a (DSL)fill asymmetry + align-self single-emission + pseudo-class hoist + media-query auto-wrapUiFillCssSpec + UiAlignSelfSpec + 39-example sweepSOLID (v0.15.55-57 close)
Std.HtmlCompositional generatorsEscape contractInspectionSOLID
Std.Live runtimeTEA shape + Cmd/Sub monoidSSE + XSS hardening + input preservationMany compiler-test specs + 39-example sweep + PlaywrightSOLID-with-caveats (password rule docs-only)
Std.Dbn/aSqlValue + tenant SQL gate + migration checksumrt/db tests + v0.16.6 gateSOLID (v0.16.26 close)
Std.Authn/aTyped secret + bcrypt + JWT exp/nbfrt/auth_test.goSOLID-with-gap (no Argon2id)

7.2 Overall verdict

SOLID across the stdlib surface area with three documented caveats:

  1. Std.Ui correctness is verified at the EMISSION level (UiFillCssSpec, UiAlignSelfSpec) but visual-regression is by the 39-example sweep, not pixel-diff. A CSS-engine regression in Chromium/WebKit/Firefox could break user code without Sky noticing.
  2. Stdlib algebraic laws (Functor / Monad on Maybe / Result / List / Task) are verified BY INSPECTION, not by property test. Adding QuickCheck-style law specs is documented as a future gap.
  3. Password-form rule is documentation, not a static check.

The "no runtime panic from well-typed Sky code" non-regression rule (CLAUDE.md Rule 8) is enforced by:


8. Critical gaps + pending regression tests

Listed in priority order. Each is actionable — there's a clear implementation step OR a clear spec to write.

8.1 High priority

G1. Algebraic-law property specs (Maybe / Result / List / Task).

G2. Grapheme-cluster API on Strings.

G3. Limitation #8 regression sweep at 1M elements.

G4. Password-form static check (LSP lint).

8.2 Medium priority

G5. Argon2id password hash variant.

G6. Ed25519 / X25519 in Crypto module.

G7. Cmd.batch ordering specification.

G8. Sky.Tui pseudo-class / animation gating.

8.3 Lower priority

G9. JWE (encrypted JWT).

G10. JWK / JWKS discovery for RS256.

G11. Cross-process Lazy cache.

G12. Migration rollback (DOWN scripts).


9. Read this together with the compiler doc

The companion docs/rust-rewrite/ (07-lowering-and-ir.md, 08-go-codegen.md, 09-runtime-and-ffi.md, and 14-runtime-narrowing-taxonomy.md for the narrowing floor) explains:

The two documents together cover the full pipeline:

flowchart LR
    Source[Sky source] -->|This doc: stdlib surface contract| Stdlib[Stdlib usage]
    Source -->|Compiler doc: parsing / canon / HM / lowering| Codegen[Go IR + runtime]
    Stdlib --> Codegen
    Codegen --> Output[Typed Go binary + runtime]

When a user asks "is this Sky code mathematically correct?", route to THIS doc. When they ask "how does Sky compile this code?", route to the compiler doc.


Last updated: 2026-06-23. Compiler tracked: v0.16.6 RC + v0.17 in flight on feat/v0.17-fully-typed-codegen.