Repository navigation
Releases: DonaldMurillo/gofastr
Release list
v0.88.2
Fixed
- A page can no longer freeze while a navigation waits for its
sheets. On a page where thefullbundlemodule left a
data-cui-bundle-hrefmarker in place (markup carrying the attribute
in its content, or a module from another release), every navigation
waited for the module, found the marker again and asked again, in a
loop that starved the page's timers and input. The wait now asks for
the module once. It is also bounded at 3 s, like a sheet, so a
fullbundle.jsfetch that never answers no longer holds every
navigation and the scroll settle for good. - A cached or prefetched screen honours its loading region. Since
the sheet wait, a loading region whosedata-cui-aftertimer fired
during the wait was cut off by the commit on a cached replay or a
prefetched screen, with nodata-cui-minhold and no exit animation.
Both now hold and exit as a fetched page does. - Clicking A, B, then A again no longer fetches A a third time.
When the first A's late answer came back, its cleanup dropped the
in-flight entry the second A owned, so a further navigation to A
fetched again and replaced the live one. - A tap on a control below an open popup reaches that control. A
dismissable disclosure closed on the touch release, before the
browser aimed the tap's click, so an in-flow panel collapsed and the
click landed on whatever moved under the finger. A tap on a link,
button, field, label or summary now closes it through that click; a
tap on plain content still closes it on the release. - An ordinary navigation drops a record pane still loading. A pane
waiting for its sheets when a plain navigation started could still
open and push its record URL, so the page that then loaded sat under
the record's URL. A pane that came back declined or failed fell back to
a plain navigation to the record, replacing the newer page. The pane
is now dropped once any navigation starts, whatever its answer. - The docs site's phone menu scrolls on a short screen. On a phone
held sideways the open menu ran past the bottom of the viewport and
its last links could not be reached. It now stops at the viewport
edge and scrolls. - The docs site's phone menu rows are full tap targets. Each link
in the open menu was a 28px row of 13px text, short of the 44px touch
target and smaller than the page's body text. The rows are now 44px
tall at the body size, and so is the menu's search field text. The
open menu also sits above a page's floating section pill, which
otherwise covered its last row. - Page switches on a static export take one request, not two. An
exported page lives at<path>/index.html, which GitHub Pages, S3,
Netlify and Cloudflare Pages serve at<path>/and answer<path>
with a 301. Every link was written as<path>, so each click paid a
redirect round trip before the page arrived. Links to exported pages
now carry the trailing slash; on a throttled mobile connection a
docs-site switch went from about 465 ms to about 315 ms. An installed
PWA's service worker keys its precached pages the same way, so a
client-side switch still finds them offline, and a hidden widget's
chrome (a section menu opened from the header) and the PWA offline
page link them the same way too. A link the export does not write, such as a search result,
still lands on the page, and the current-page highlight now treats
/aboutand/about/as one page. - A static export's sitemap names its base path once. A site whose
SitemapConfig.BaseURLalready ends in the export's base path
(https://donaldmurillo.github.io/gofastrwith--export-base /gofastr) listed/gofastr/gofastr/...URLs in sitemap.xml and
robots.txt, and every one of them 404ed. The base path is now added
only when the URL does not already end in it, and each page URL
carries the trailing slash the export's links do.
v0.88.1
Fixed
- A client navigation no longer paints the new page unstyled. Since
v0.88.0 each page links only the component sheets it renders, so
nearly every navigation brings markup whose sheet the current page
never loaded. The runtime swapped first and linked the sheet after,
and on a phone with a slow connection every page change flashed
unstyled before the styles arrived. The navigator now scans the
incoming markup, links its sheets while the old page is still on
screen, and commits once they load. It waits only on the sheets that
markup renders, never on an unrelated one still in flight, and each
sheet is bounded at 3 s so a stalled fetch delays one navigation and
never freezes the next. The page title, the screen-reader route
announcement and a loading region'sMinhold and exit animation
all run after the wait. This covers every swap path: fetched
partials, whole documents, cross-layout swaps, cached and prefetched
screens, envelope fills, intercepted panes, and a page that has not
yet linked its full bundle after a first-paint subset. A deferred
part lands only once its own sheets have loaded. A hover prefetch
starts its page's sheets when it lands, so the click usually finds
them loaded; visible and eager prefetches leave them for the click.
An envelope module served beside an older cached kernel still
navigates, without the wait. The core runtime grows 200 bytes at
gzip level 1 (15556 → 15756). - Going back to a page whose load is still in flight works. Click
A, then B, then A again before either answers: the runtime joined
the third click to the first A request, which B had already
superseded, so B rendered under the URL/a. A navigation now joins
only an in-flight request that is still the live navigation. - Headless disclosures light-dismiss on a touch. A disclosure with
Dismissalso closes on the release of a touch or pen press outside
it. iOS Safari sends no click for a tap on content that is not
clickable, so a tap on the page left the popup open. - The docs site's phone menu closes on a tap outside it, and its
close button stays reachable. The panel hung from the top of the
viewport, so on the static export, where a preview banner sits above
the bar, the open panel covered the bar and its toggle. It now hangs
from the bar.
v0.88.0
BREAKING. v0.88.0 stops first paint waiting on the runtime, and a
live page whose head sheets fit 16 KiB gzipped carries them in its
first response (a heavier page inlines the rules its markup can match
when those fit; static exports and a policy that cannot take hashes
keep the links).
It adds browser-local data (core-ui/localdb,
framework/localentity), site search (battery/sitesearch), static
export (gofastr export) and the app binary as an OS service
(battery/service, experimental), and fixes the defects of the
regression audit. Four changes break callers:
ui.ContentRowConfig.BelowHeader and gallery.ContributeCSS /
BaseCSS are removed, live pages read the route graph from
/__gofastr/manifest.js, and core/config enforces validate: tags.
Run gofastr upgrade --to v0.88.0 before you bump go.mod: it points
at each line of an app that needs an edit.
Added
battery/service: the app binary as a per-user OS service
(experimental).service.New(Config{ID, Name, Port})and
svc.Run(ctx, os.Args[1:], build)give a binary the verbs
service install|uninstall|start|stop|restart|status|logs|open|mcp|run|rotate-secret.
installcopies the binary to a managed path and registers it as a
launchd agent (macOS), asystemctl --userunit (Linux) or a
logon-triggered Task Scheduler task (Windows), then starts it on the
fixed loopback port; a held port fails the start instead of moving.
Every request passes a Host pin, a cross-site refusal and a signed
session cookie, whichopenmints by handing the browser a 0600
local page that POSTs a 60-second single-use token. CLI clients reach
the daemon over a Unix socket in the 0700 state directory, checked
against the daemon's OS user by peer credentials. A signed session is
the local user, so owner-scoped entities work withoutbattery/auth;
with auth linked, auth keeps identity. Agents reach the App's MCP tools
throughservice mcp, a stdio server tunnelled over that socket and
run as the same local user;Runtime.MCPCommand()gives the argv with
--state-dir, so it reaches the right daemon from any directory. The
dev run keeps its own state directory and port (one per isolated
worktree), andgofastr devruns it gated on the address it passes.stop, reinstall and
uninstallwait for the old process to exit, and an instance lock
keeps one process per state directory. Windows has no crash restart,
and its backend is unit-tested but has not run on a real Windows
machine. Guide:gofastr docs service; example:
examples/gofastr-runs, which lists the machine's running gofastr
apps and serves the list to agents.- Running Apps record themselves in a local run registry.
App.Start
writes a 0600 JSON record (name, pid, URL, MCP path or command,
service id, dev flag) into<UserConfigDir>/gofastr/runsonce the
port is bound and removes it when the server stops.
framework/localrun.Listreturns the live runs and deletes the
records of dead pids.GOFASTR_RUNS=offopts a process out,
GOFASTR_RUNS_DIRmoves the directory, and test binaries, and the
apps they start, skip it unless the directory is set. Guide:
gofastr docs local-runs. App.WrapServerwraps everything the listener serves. Router
middleware (Use) never sees the interactive setup wizard or the
worker and agent roles' reduced muxes; aWrapServerwrapper does.
The first one registered is outermost, and a call afterStarthas
bound the server, or afterShutdown, panics.ui.BoxConfig.Boundedcaps a Box at the--ui-box-bounded-sizeknob
(20rem) and scrolls what overflows: a window for a long list in a panel,
or for previewing a viewport-height component such asui.Workbench.ui.TerminalErrmarks a failure line in aui.TerminalBlock
(danger tone,--ui-terminal-block-err-colorknob), beside
TerminalOutandTerminalOK.HeroConfig.Lede(inline markup in place ofSubtitle) and
HeroConfig.Footer(content under the actions: an install line,
a byline, a stats line).SectionConfig.Bandgives a section without an eyebrow the
page-band treatment (display-size heading, more air under the head).ui.StackConfig.StickyHeaderpins the page frame's<header>and
publishes its height (--size-header-height) as--ui-sticky-top.
Every top-sticky part below reads it, at any layer depth: aSticky
ContentRow's nav column and toolbar,ui.StepRail, the
interactive.SectionMenurail,ui.TableOfContents,
ui.AnchoredRail,ui.GlobalSearch,ui.NetworkRetryBanner, a
top-edgeui.Sticky, aFormFrameside panel and an anchored
ui.Sectionstop under the header instead of sliding behind it. A
region that scrolls on its own (a boundedui.Box, the pinned nav
column, aui.ListDetaillist, the PaneHost drawer, the Workbench
rail) resets the offset to 0. The layout declares the sticky header
once; no component takes a flag for it.SectionMenuConfig.Filterputs a filter box above an
interactive.SectionMenu's list, in the rail and in the phone sheet.
Typing hides the links that don't match, marks the match, shows
"matches/total" in each group header and saysFilterEmptywhen
nothing matches; Escape clears it.FilterPlaceholdersets the
prompt.InlineTriggerkeeps the phone pill in flow, for a menu shown
inside a card or a demo.SectionMenuConfig.SwapAtmoves the width where an
interactive.SectionMenutrades its rail for its phone pill
from 900px to aui.ContentRowbreakpoint (interactive.SwapMD,
48rem, orinteractive.SwapLG, 64rem), so a menu in a row's nav
column swaps where the row stacks.ui.TOCConfig.PhoneSheetfolds a contents list into a bottom sheet
on phones. BelowBreakpoint(md by default) a pill floating at the
bottom of the screen names the section in view and opens the list;
at and above it the list renders as before. The sheet is a native
popover rendered with the page: it opens without script, closes on
Escape, a tap outside, its close button or a followed link, and with
script moves focus to the current entry when it opens.
ui.ContentRowConfig.PhoneAsideFlushdrops the stacked aside's rule
and padding so an aside holding only that list leaves no band.
The pieces are public:interactive.SheetPill(the pill SectionMenu
uses too) andinteractive.PhoneSheet(pill plus inline sheet for any
list that marks its current entryaria-current).
ui.AnchoredRailConfig.PhoneSheetfolds a rail the same way (below
SwapAt, 900px by default); its pill names the section by the link's
text, without the parts markedinteractive.SheetChipClass(the
rail marks its number and count chips).PhoneSheetTopon
either (interactive.PhoneSheetConfig.Top) pins that pill as an "On
this page:" bar under the sticky header and hangs the list
from the bar, for a page whose SectionMenu pill already holds the
bottom edge. In a ContentRow aside the bar's aside moves above the
main column on a phone and sticks there. Anchored sections and the
rail's in-view band clear the bar through--ui-sheet-bar-offset.- entityui lists warn when their row links lead nowhere. The first
time a list renders rows whose<base>/<id>matches no registered
screen, it logs one warning per base naming the route to register.
Until now every row link 404ed with nothing in the log. The check runs
throughApp.EntityUIwith a mounteduihost(UIHost.HasScreen);
a custom host opts in throughentityui.ScreenHost.NoLinks()lists
skip it, and it remembers at most 256 bases (#503). - The startup banner says how to get past the 401s. When an entity
requires auth and nothing on the app signs callers in, the banner
prints one line: wirebattery/auth, or setExposure.Public.
battery/authcalls the newApp.NoteAuthenticator()onceInithas
run and a middleware that resolves callers exists
(SessionMiddleware(mgr),mgr.Middleware(), or
RequireAuth(mgr.JWT())), so anInitwith no middleware keeps the
hint up.battery/desktopcalls it when its window identity takes
effect; a custom auth layer or aTokenMiddleware-only app calls it
too, andApp.AuthenticatorNoted()reads it back (#503). - An unknown
?include=names the relation it was close to.
?include=issueon an entity whose relation isissue_idnow answers
did you mean "issue_id"?. Only a near miss (the_idform, the
foreign key, or the target entity) gets a suggestion; the error never
lists an entity's relations (#503). gofastr initscaffolds sign-in. The sample app shipped CRUD
that answered 401 with no way to sign in. It now wiresbattery/auth
(users and sessions in the app's database,SessionMiddleware) and
writesscreens_auth.gowith/loginand/register; the login form
carries?next=through to the redirect. Registering
lands on/login?registered=1with a notice rather than a session,
because the register endpoint answers a new and a taken address alike.
Entity CRUD mounts under/api, so a screen can own/posts..env
gains a per-checkoutJWT_SECRET. Auth dev mode followsGOFASTR_DEV
(set bygofastr dev), never.env, so outside the dev loop auth
issues Secure cookies and refuses to boot without a secret.init .
refuses a directory whose.envgit already tracks (#503).ui.AuthCardhas aNoticeslot. It renders arole="status"
message in the success color between the alert and the body, for
outcomes like "account created" that the danger-styledAlertwould
misreport (#503).battery/sitesearch: full-text search over every rendered page.
The battery renders the pages a static export would write (dynamic
routes throughStaticPaths), indexes each page's<main>with its
h2/h3sections as deep links, and answers at
...
v0.87.2 — gofastr dev answers while the app is down, isolation remaps a port once
Added
gofastr devanswers while the app is down. During the first
build, after a failed build, and after the app exits, the dev address
returns 503 withRetry-Afterand says which of the three it is, the
build or lint output included, instead of refusing connections. Open
tabs reload into that page when a build fails and back into the app
when it is up again. A secondgofastr devon a busy address now
stops at startup instead of after its first build, except in a linked
worktree, where isolation moves it to the next free port (#483).entityui.UI.WithCreatePathsets the create screen's route. An app
that mounts its create screen at/deals/newinstead of
/deals/createpasses one path function, and New, Duplicate, Create
another, related-list Add and relation-picker New all link there.
Returning false or an empty path hides an entity's create links.
Without the option, links still use<base>/create(#502).
Fixed
- Entity state-move buttons no longer fail with HTTP 415. The RPC
runtime now sendsContent-Type: application/jsonon writes that have
no body, which the transition route requires. GET and HEAD send no
content type, and multipart forms keep the browser's boundary (#500). - Worktree isolation remaps a listen port once. In a linked
worktree,gofastr dev --addr localhost:8791printed one port while
the app bound another: the child remapped the address it was handed,
andApp.Startremapped the result ofisolation.ListenAddragain.
Runtime.Envnow passes every recorded mapping to the child, and
active runtimes in one process with the same project, isolation ID
and port config share their mappings, so the app binds the addressgofastr devnames.
v0.87.1
Changed
gofastr initwrites the agent detail files to.agents/. They
went to a visibleagents/folder at the project root, beside
entities/,migrations/and the app's own packages. They now go to
a hidden.agents/folder, andAGENTS.md,CLAUDE.mdand the
gofastr-host skill link there. Rungofastr agents sync(or
gofastr init --reinit) once in an existing project: it writes
.agents/, deletes the old generated files inagents/(only files
with the auto-generated header, so a hand-written file there stays)
and removes the folder once nothing else is in it. It also points the
generatedagents/references inAGENTS.md's intro at.agents/,
and--reinitrefreshes a stockCLAUDE.mdfrom the earlier release.
Anagentssymlink is left as it is.
Added
- A sidebar can stay pinned under a sticky page header.
ui.ContentRowConfig.BelowHeader, withSticky, sticks the nav
column and the toolbar below--size-header-heightinstead of behind
the header, and makes the column the rest of the viewport tall, so a
long menu scrolls on its own while the page scrolls beside it.
interactive.SectionMenuConfig.FillColumnmakes a section menu fill
that column. The docs site's/componentsmenu uses both.
v0.87.0
BREAKING. v0.87.0 reskins the default theme, gives entities screen
hints and state machines, draws entity screens through the new
framework/entityui, and rebuilds the admin on it. It deprecates
nothing: a removed API is gone. What changed in
v0.87.0
(gofastr docs release-0-87) is the full account, with every breaking
change, what replaces it, and the fixes this release carries. Run
gofastr upgrade --to v0.87.0 before you bump go.mod: it points at
each line of an app that needs an edit.
Highlights
- Zinc default theme.
style.DefaultTheme()andtheme.Default()
draw a near-black primary on white with 6/8/10/14px radii. Stroke,
line-height, letter-spacing and opacity are tokens (Theme.Strokes,
Leading,Tracking,Opacities),Theme.Knobssets any
--ui-*knob, and every kit dimension reads a token or knob, so a
theme alone can redraw the kit (theme.Brutal()). ui.ThemePickerswitches a page between registered themes and
remembers the choice.- Entity
Display.EntityConfig.Displayholds names, title
fields, columns, named views, facets, form layout and field hints,
checked at registration.dsl.ParsePredicate/ParseSort, ane
filter,crud.ListOptions.Where,SumAllandGroupCountAllback
it. - Entity
States.EntityConfig.Statesnames a state field and
its moves. Every write path enforces them; a move runs through
RunTransition,POST <api>/<entity>/<id>/transitions/<key>or an
MCP tool, and is audited.crud.WithStateOverridewrites outside a
move with a recorded reason. framework/entityui.App.EntityUIdraws list, record and create
screens with no islands: views, facets, filter chips, saved views,
inline editing, bulk actions, CSV export, a trash view and status
overrides.framework/ui/resourceis deleted.- The admin, rebuilt.
battery/admindraws entity screens through
entityui at/admin/entities/<entity>, renders every page through the
app's UI host, and ships no CSS. New dashboard, command palette,
saved views and queued bulk runs. - Prefixes. Kernel attributes are
data-cui-*and kernel classes
cui-*;fui-*isframework/ui's alone. - Security audit. The 2026-10-04 audit of the request path fixed
scope, relation, upload, 2FA, verification and rate-limit gaps, each
with a security test. The contract catalog holds 78 rules.
Breaking, in brief
- A
ShadowSetbuilt field by field (everytheme.goan earlier
gofastr theme initwrote) needsShadows.XS, or it panics at boot.
--color-secondary,--color-border-strongand--color-accent
change role. - Owned sheets must read the stroke, leading, tracking, opacity and
logical spacing tokens: GOFASTR1807 refuses the matching literals. data-fui-*kernel attributes and the kernel'sfui-*classes are
renamed todata-cui-*andcui-*;.fui-copied,
fui-toast-stack-autoandis-leavingare hooks now.framework/ui/resourceis removed;battery/adminneedsConfig.UI
and a UI host, moved its routes, and lostTheme,FontFaceCSS,
EntityListLimit,Secret,RegisterRoutes,SortDirOfand
/admin/rbac/_revoke.- The blueprint's screen
filters:,transitions:,search:,
island:andwidget:keys and theentity_create/entity_edit
blocks are gone. - The
framework_docs_*MCP tools need
framework.WithMCPTools(mcptools.Register). - Filter operators must suit the column type, and
crud.ListOptions.Filtersis validated. i18nui.LabelForField,RenderOverlayResult's arity,
queue.Browsable.ListJobs's arity,WorkbenchConfig.RailWidthand
CopyButtonConfig.Targetchanged.- Auth and access: unverified accounts are claimed on mailbox proof,
RequireTwoFAjudges the request principal, scoped tokens and embed
grants are held to their scopes across relations,X-Forwarded-For
is read from the right,/.debugneeds the admin role, and
upload.Handlercaps bodies at 32 MiB. App.WithAuditLogadds areasoncolumn to an existing audit table
at boot.
Security
- The repo builds with Go 1.27.2 (
toolchain go1.27.2in go.mod; the
go 1.27.0floor for apps is unchanged), which fixes the thirteen
standard-library advisories govulncheck reports against 1.27.0 (among
them GO-2026-6599 and GO-2026-6600 in html/template). Build apps with
1.27.2. golang.org/x/tools moves to v0.50.0. - Security audit findings 83–100: bounded growth and lifecycle fixes
across the auth, cache, queue, relay, stream, cron, rate-limit and
credential-store packages. Each is listed under "Security hardening"
in the release page's Fixes section. a2a.Config.MaxConcurrentRunsPerOwnercaps one owner's concurrent
skill-handler runs, resumes included (0 = 16, negative = no cap).PasswordResetPlugin.OnStopstops the reset-email workers at
shutdown; reset email goes out from a bounded queue, off the request
path, so a known address answers as fast as an unknown one.credstore.ErrAmbiguousLegacyKey: a legacyprovider|accountkey
with more than one|is no longer handed to the first split;Get
asks for re-entry andPut/Deleteon any pair that could own it
replace it.
v0.86.0
BREAKING. v0.86.0 rebuilds the UI layer and deprecates nothing: a
removed API is gone. What changed in
v0.86.0
(gofastr docs release-0-86) is the full account, with every breaking
change, what replaces it, and the fixes this release carries. Run
gofastr upgrade --to v0.86.0 before you bump go.mod: it points at
each line of an app that needs an edit.
Highlights
- Headless components. Every
framework/uicomponent renders
throughframework/headless, which owns structure, ARIA and the
data-hui-*hooks behaviour binds to.core-ui/patternsis deleted,
and the kernel's per-component runtime modules moved beside their
components as registered behaviours. fui-*classes. Every class the kit emits is renamed fromui-*
tofui-*. Sheet names anddata-fui-compmarkers keep their names.- One layout primitive.
app.NewLayout(name, spec, build)replaces
the fixed header/sidebar/footer template, with outlets, fills, route
areas, screen groups, resolvers, deferred outlets, loading content and
transitions.ui.SiteHeader,ui.SiteFooterandui.DocLayoutare
deleted:gofastr generate packagecopies owned replacements into the
app. - Owned styles. An app's CSS lives in
<name>.style.csssheets
scoped to a layout, screen, component or the app, generated into typed
Go bygofastr generate stylesand checked against the theme's tokens
by GOFASTR1801 to GOFASTR1822. The contract catalog holds 77 rules. - Theme. Size and font-weight tokens,
Theme.Layoutpage
dimensions, typed app tokens (Theme.Extend,<name>.tokens.css),
component options, and a contrast check at boot. - Upgrade tooling.
gofastr upgradereads a project through the
type checker. Tests fail on retired markup a page renders and on render
panics.
Breaking, in brief
- Button and form wiring is typed: an unknown
data-fui-*key panics at
render.FormFieldConfig.Inputis a builder closure. ui.DataTableandui.Paginationtake typedQueryprops instead of
href patterns.- The fixed-template layout API and the
--ui-layout-*and
--ui-container-*variables are gone. Page dimensions are
Theme.Layouttokens. - Ten legacy colour aliases are gone, auto-named scale tokens spell
2xl/3xl, andstyle.ThemeRef.Hashis a method. - A theme with a hex primary or danger pair below 4.5:1 contrast panics
atWithTheme.
v0.85.0
Security
- Round-4 adversarial probes: 54 over twelve property families the
earlier rounds never opened (authorization at derived surfaces,
layer-mismatch parsing, cheap-write state growth, transaction ordering,
account lifecycle, dev surfaces in production, signed-payload confusion,
untrusted project directories, invisible/bidi characters,
interactive-layer caps, data at rest, config time-of-use), all fixed
and promoted to permanent security tests. The changes worth naming:- Bearer JWTs fail closed on lifecycle change.
RequireAuth
re-resolves the subject via the user store on every request, so a
deleted or role-downgraded account loses access at once instead of at
token expiry, matching the session and API-token planes. - Delivery counts every attempt. The outbox relay increments
attemptsat claim (matching the queue and webhook batteries), so a
relay that dies between claim and settle no longer redelivers forever;
a non-cooperative queue handler runs on its own goroutine and stops at
the deadline instead of wedging the worker. - Uploads and journals refuse symlinked leaves.
core/upload
LocalStorage refuses a key that folds onto an existing object (the
battery/storageposture, now shared); the kiln journal opens its
session fileO_NOFOLLOW; a failed multipart create deletes the file
parts it saved instead of orphaning them. - Numbers survive the wire. MCP
tools/calldecodes arguments with
UseNumber, so an integer filter above 2^53 keeps every digit and
addresses its row (small numbers still arrive as float64); the HTTP
binder rejectsNaN/Inffloats, which slip every range guard. - Derived reads stay in scope. The admin audit listing and its
overview count filter by the caller's tenant;/openapi.jsonand
/api/docs/openapi.jsonapply llm.md's per-request read-scope filter,
withWithPublicOpenAPIthe documented full-disclosure opt-in. - Subscriber seats are bounded. The SSE bus, MCP notification stream,
ACP sessions, and CRUD event streams cap concurrent seats per principal
(default 16) with a configurable overflow policy (refuse, the default,
or evict-oldest); A2A task rows and push-notification configs are
bounded per owner. - Dev and build inputs are contained. An exposed (non-loopback) dev
MCP bind withdraws its mutating tools; the harness.envloader and
the project isolation file refuseHTTPS_PROXY,PATH,LD_PRELOAD
and other process-shaping names from an untrusted checkout; the config
loader rejects non-finite floats. - Widget gating gains a level.
RequireSessionkeeps its meaning (a
browser session, anonymous allowed, per-session scoping) with its doc
corrected; a newRequireAuthenticatedfails closed for the anonymous
session the first page load mints. - Invisible and bidi characters (C1 controls, zero-width, Trojan-Source
overrides) are stripped or refused at the sinks where they forge or
hide identity: email canonicalization, notify and email headers,
upload keys and filenames, page titles, log and trace lines, generated
blueprint enum values, the verify report, and the harness terminal.
The shared predicate lives in the newcore/textsafepackage.
- Bearer JWTs fail closed on lifecycle change.
- Three static-analysis rules from the round-4 bug shapes, taking the
contract catalog to 59 rules:GOFASTR1410(a local-filesystem
Save/Put/Writereaching an open/create/rename with no folded-key
refusal), plus thelaxenvelopevet analyzer (one transport decodes an
RPC envelope strictly while a sibling decodes it lax) andnonfinite
(aParseFloatresult stored with noIsNaN/IsInfgate). Ten rule
shapes were prototyped; the seven that fired too broadly on
correct-by-design sinks were dropped, their target bugs covered by the
fixes above.
BREAKING (round 4): an MCP tool handler's plain error is now
answered to the caller as a generic "internal tool error" (its text is
logged server-side); a handler that means its message to reach the caller
returns an *mcp.RPCError or an mcp.ToolResult{IsError: true}, the form
mcp.Gated and the framework's own tools now use. A duplicate multipart
form key is refused for a scalar field and collected into a list for a
schema.JSON field, matching the JSON body path. /openapi.json
and /api/docs/openapi.json now filter entities by the caller's
read scope (an entity the caller cannot read is omitted, matching
/api/llm.md); WithPublicOpenAPI opts into the full, unfiltered
document.
- Round-5 adversarial probes: 130 probe files, 158 failing findings
and 2 data races over the families the earlier rounds never opened
(log-side scrubbing of panic payloads, stream-seat exhaustion,
cache suppression on per-user responses, secrets at rest, strict
decode at every inbound seam, terminal-state settles, missing
deadlines, argv-exposed credentials, identifier gates in emitted
code, panic containment at host callbacks), all fixed and promoted
to permanent security tests. The changes worth naming:- Session gates refuse API tokens.
RequireSession,
SessionPolicy, andRolePolicyanswer 401 to a caller
authenticated only by a scopedgfsk_token (TokenScopesin the
context is the discriminator), so a leaked scoped token cannot
reach the SSR screens its scopes never named.RolePolicynow
honours anaccess.Decider. - Password-equivalent tokens are hashed at rest. Magic-link,
password-reset, and email-verification tokens store
sha256(token) keyed for lookup (theAPITokengrammar), and
EntitySessionStoredoes the same for the session cookie value:
a read-only dump of the token or session table is no longer a
pile of live takeover credentials. The session cookie is minted
by one helper (mintSessionCookie) and carriesSameSite=Strict
on password login, magic-link verify, and the OAuth callback.
BaseURLon the three link-minting plugins must be an http(s)
URL orInitfails; the OAuth refresh-token save is a
compare-and-swap (SaveIfRefreshUnchanged). - CRUD writes gate their foreign keys. A create/update whose
BelongsTo FK names an owner/tenant/read-scoped row the caller
cannot read answers 404, the same status reading that row gives,
so existence is not leaked.owner.AllowCrossOwner, the target's
CrossOwnerReadpermission,tenant.AllowCrossTenant, and
WithServerWritescontexts pass unchanged. CRUD responses carry
Cache-Control: no-store; typed delete hooks error on drift and
typed hooks merge nested in-place mutations; pagination cursors
refuse duplicate and case-folded keys at any depth. - Streams admit by seat.
rtc.Config.MaxSocketsPerUser
(default 16, evict-oldest per principal) caps signaling sockets;
a2a gainsMaxStreamSeatsPerOwner(0 = 16, negative unlimited)
andStreamSeatOverflow(Refuseanswers 429,EvictOldest
closes the owner's oldest stream); the harness control plane
shares onecontrol.SeatTable(DefaultStreamSeats16) across
its REST SSE, WebSocket, and MCP GET streams; kiln's
/.kiln/eventsseats 16 per peer host. - Per-user responses suppress storage. The auth token and
account listings, CRUD responses, A2A POSTs, uihost pages
(no-store plusVary: Cookie), the resourceTableHandler, kiln
JSON GETs, the harness REST surface, and the generated chat page
all stampCache-Control: no-store: RFC 9111's storage
restriction covers Authorization headers, not cookie
authentication. The agent-card and Link-header pagesVaryon
HostandX-Forwarded-Protowhen noBaseURLis pinned, and
the idempotency middleware appends toVaryinstead of
overwriting it. - Strict decode at every inbound seam.
handler.Bind's
non-struct branch is gated on a JSON Content-Type, query params
truncate at the first C0/DEL byte, andbindBody/
UnmarshalStrict/DecodeStrictrefuse trailing JSON. MCP
prompts/get and resources/read, moduleproto frames
(ErrInvalidFrame), ACP params (id-matched invalid-params at any
depth), the harness mcpserver (refusals are -32602 with an
any-depth key walk), OpenAI chunks and the OpenRouter /models
listing, rtc signaling frames, and the uihost?theme=embed all
decode strictly now. - recover() output is scrubbed.
textsafe.Recoveredrenders
every panic value that reaches a log (auth audit and notify,
semantic metadata, a2a invoke, the fanout subscriber, mcp gates,
the middleware collector, stream close hooks, the semantic
watcher), stripping control bytes and truncating.StateChannel
also survives a panicking hostSnapshotFor/FilterEvent
callback. - State files are owner-only on overwrite.
os.WriteFilewith
an 0600 literal only applies the mode at create, so overwriting a
0644 file kept 0644. The sharedfileperm.WriteOwnerOnly/
SeedOwnerOnlyhelpers (open, chmod the handle, write) now back
the harness memory/credstore/persist/tracing/DEK writes, kiln's
world.jsonandsession.db, and the generated CLI's
config.json. - Terminal settles are fenced. The queue's Redis
Reclaim
drops stale entries through a fencedreleaseClaim; webhook
inbound envelopes never regress a terminal status (memory and
SQL, both dialects); the outbox SQLite backoff predicate matches
its Postgres sibling; admin queue replay writes its audit row. - Deadlines on every probe. Bare
http.Get/Post/
http.DefaultClientclients are gone from kiln's port probe, the
bench readiness loop, semantic remote calls, and the eval probe;
generated e2e/axe suites use a 10s-timeout client and the
semantic CLI a 2s one; exec children with c...
- Session gates refuse API tokens.
v0.84.1
Fixed
- Client-side navigation works on a static export below a base path.
The export rewrote every href for the base and left the embedded route
graph root-relative, and the runtime takes the client-side path only for
a link it finds in the graph, so on a GitHub project page every click was
a full page load. The graph's paths carry the base too. - A static page no longer loads every component stylesheet twice. The
export emitted one<link>per component without thedata-fui-style
marker the runtime dedupes on, so on load the runtime appended a second
copy of each afterapp.css, and a host's own overrides lost the
cascade: a sidebar title stretched to fill its column, a doc layout
collapsed. The exported links carry the marker and id the runtime would
have written.
v0.84.0
Fixed
- A static export renders layout chrome for the page being exported.
uihost.RenderStaticPagerendered with neither a request nor a route
match in the context, so a layout component that reads the path, such as
a sidebar for the section being read or a header whose language follows
the page, rendered for no page at all: every exported page carried the
chrome of the site root, and a translated page shipped with the original's
sidebar. The static render now carries a request for the path and the
route match, exactly ashandlePageinstalls them for a live request.