Repository navigation
hecks 6.0.0
-
AwsBoxno longer runs a tunnel, and a world that declarestunnelis refused. A box is reached through its CDN or load balancer and Caddy with an origin-secret header (ADR 0156). Thecloudflaredservice, its outbound rule, its token secret and its post-roll check are gone from the generated stack; the message a refused world gets namescdnandorigin_secret. A project that declared no tunnel is unchanged. -
A Rails app over a bluebook has working pages with no view files of its own.
hecks-railsrenders a list for each aggregate that takes commands, with a form for each creating command; a record page that offers only the commands the signed-in person may run now and names the ones the domain would refuse; and, when a command is refused, the same page again with that command's form open, the person's input kept and the domain's reasons beside the fields. An app's own view of the same name wins. A project changes the pages withmemberrows of a value object namedApp(title,home) orListing(aggregate,label,columns) in its own bluebook; the newRailsAppbluebook (lib/hecks/rails_app/bluebook/rails_app.bluebook) names the fields a row may carry, and with no rows the pages are derived from the domain alone. The:rails_appprojection now draws a root route to the home aggregate's list and writes a controller only for an aggregate that takes commands, so one that only holds rows has no page. On a development server the pages add an "Acting as" picker of the roles the commands name: it signs the session in asdev-<role>, tells Governance that actor holds the role, and answers 404 anywhere else (Hecks::Rails.dev_actor = trueorfalseoverrides the environment). -
Dispatcher#state_admits?answers whether a record's state lets a command run, before any argument is known.validatestops at the arguments stage, so a command with an argument still missing never reached the lifecyclefrom:stage and a record that had moved past it still looked ready.state_admits?asks the two guards a dispatch runs, reads no argument, and is howhecks-railsoffers a record's commands without naming the runtime's classes. It is Ruby-only on purpose; the ADR 0128 addendum says when it should get a Rust counterpart. -
command_form_forcarries the forgery token, and a refused command's errors stay on its own form. The form had noauthenticity_tokenfield (the helper it asked for is private), so a browser post failed withInvalidAuthenticityTokenwhenever protection was on; it now adds the token when protection is on. A newerrors:option (default true) marks the fields the last refusal was about, and the record page passes it for the refused command only, so another command beside it that shares a field name is not marked. -
An input's
patternis translated for the browser (Hecks::Forms::HtmlPattern). A browser reads the attribute as JavaScript, in itsvmode, and holds it to the whole value, while the domain reads a pattern as Ruby and finds it anywhere in the value. So[^ \t\n\r]accepted one character,^https?://accepted only the scheme, and a pattern using\A,\z, or a bare-or{inside a character class could not be compiled and was ignored. The pattern is now wrapped to match anywhere,\A,\z,\Zand\hare written as JavaScript has them, and-,(,),{,},|and/inside a character class are escaped. The named patterns and the forms the bluebooks declare compile as JavaScript, which a spec checks in Node when it is installed. -
Five edges a person wiring a domain meets first are smoother. A policy reaction the domain refused (and nothing excused) is warned on stderr by the dispatch that started the cascade, as
[hecks] reaction <Policy> was refused (<trigger>): <reason>, so a policy that did not take is seen by Ruby callers as the CLI already showed it; a crash was already warned. The bulk form on an aggregate module (Order.complete!) called withoutover:now says it is the bulk form and that the one-record form is called on the record's handle, instead ofmissing keyword: :over. An adapter's constructor may nameregistry:andaggregate:: it is handed the asking registry (not whichever registry a launcher holds) and the aggregate asking out, and an adapter that names neither is built bare as before; a constructor that requires anything beyond those two is still refused at boot. A sentence-form.bluebooknow loads wherever a Ruby one does (attaches, framework members), not only from a project folder; it still needshecks-parsebuilt. -
A journal can prove it was not rewritten: the Ledger chapter, and the Rust host seals as it commits (ADR 0133).
lib/hecks/hecks/ledger.bluebookaddsSeal(the head of a SHA-256 chain over a journal's rows through an ordinal, and the head before it) andAudit(Verifywalks the chain from the seals and endsverifiedorunproven, naming the row or the reason). Seals live beside the journal they cover: an anchors file next to a Heki journal, ahecks_ledger_anchorstable next to a Sqlite or Postgres one. A compaction seals first and is abandoned when the head cannot be captured. On the Rust host, a commit that leavesHECKS_SEAL_EVERYor more lineage rows past the last seal (a thousand unless set;0turns it off) seals in the same transaction as the rows it covers, so a seal and its rows land together or not at all; theledger seal <Domain>andledger verify <Domain>tools seal the tail and walk the chain by hand, and exit non-zero when it cannot be shown intact. -
A
.bluebookcan be written as plain sentences, and its examples run (ADR 0131). A file whose first line isDomain: <name>is read as sentences (A page is identified by its slug.,Rule "a page has a slug" holds when the value is not empty,Command: Publish,Whenever PageDrafted is announced,Then review is asked) into the same IR the Ruby form gives, so boot,model_check, the Composer and every projection take it unchanged. What an author may say is a table of phrases in the newSentencechapter, projected intorust/parser/src/phrases.rsbyhecks language_run.project_phrase_table.hecks-parsegainsrender(the Ruby text for a chapter, which Ruby loads throughHecks::SentenceForm),format(any chapter as canonical sentences),complete(the sentences legal at a cursor) andexamples; a refusal now carries a column (file:line:column: message) and the nearest phrases, andhecks-lspstarts its squiggle there and offers completion. A*.examplesfile written as sentences (Dana drafts a page with slug "about",Sam tries to publish it,Then it is refused because only a draft can be published) is compiled to.behaviorsand run byhecks run_behaviors;.behaviorstests gainas "<role>"andsetup …, as: "<role>", which bind a caller for the dispatch (a dispatch with noasstill binds none). A command may be run by several roles (role "Editor", "Admin"in the language,Who: Editor or Adminin sentences): the IR gainsrolesonly for a command that names more than one, a caller holding any one is let through, and a refusal names them all, in Ruby and in the Rust kernel. An expression gainslist.sum { |x| expression }, the total of a number worked out for each element (0 for an empty list), read and evaluated the same in Ruby,hecks-parseand the kernel.hecks-parseis not shipped in the gem, so a sentence chapter needs it built (HECKS_PARSE_BIN,rust/parser/target/, or thePATH). The sentence form covers the whole Ruby form except a few refusals it names (docs/sentence-form.mdlists them): entities, reports, processes, queries with arguments and comparisons, closed sets and tables of members, references, lists and options on an attribute, a command's changes, states and postconditions, policies with fan-out and outside events, and a chapter's classification, version and capabilities. Anything the formatter cannot write is refused by name, never dropped, andspec/sentence_coverage_spec.rbholds the whole corpus to an exact read-back. An expression may be written inside a rule asthis is true: <expression>. Seedocs/sentence-form.md. -
hecks-parsereads a pattern in single quotes and a regexp literal. A{11}inpattern: '^[A-Z]{11}$'was taken for a block, and a#in/\A#[0-9a-f]{6}\z/for a comment, so a bluebook holding either (Emaho's and the CMS's do) was refused as malformed; the lexer now tracks'…'and/…/as it does"…". -
Breaking: a package declares its own capabilities, and
ir.jsoncarries them in oneprovidesmap (ADR 0119). The language keeps the contracts Hecks itself reads (authorization,membership,identity,privacy,subject_keys). Any otherprovidesname is the package's: each key must name exactly one thing the chapter declares (a command, query, port operation, lifecycle mark or attribute), and a key that names nothing or two kinds at once is refused at build. The product capabilities leave the language with their contracts, theirRegistry#*_provider_forlookups (usevendored_provider_for(domain, capability)) and theirExportermethods (useExporter.provides). Inir.jsonthe top-levelnewsletter,newsletter_issues,payments,registrations,payment_connectionandcheckoutkeys becomeprovides.<name>, without the derivedaggregatefields; an attribute key exports as{ "attribute", "default" }.rust/host's commerce readers andhecks-buildread and write the new shape, so a host must read anir.jsonfrom the same release. The grammar'sprovidestakes any text-valued key through one open row, and the Rust parser accepts it. Package bluebooks need no change. -
Hecks::Ports::AuthenticationandHecks::Adapters::GoogleAuthenticationare back. They were removed as unused, but the console and platform sign-in used them, and 4.0.0 broke both until each kept a private copy. The port, the Google adapter, its.adapterand.portdeclarations, theoauth2andgoogle-id-tokentest gems andspec/ports/authentication_spec.rbreturn as they were; those two apps can drop their copies once they pin a release that has this. -
Security: the Rust host bounds every kernel run.
wasm_runnerstops a run at a 10 s deadline (epoch interruption), refuses linear memory past 1 GiB, and answers an error naming the cap when the kernel's answer passes the 64 MiB output cap, where a run could before spin forever while holding the database client, or fail with a bare wasm trap. A public request whose body carries a string past 256 KiB (arg_limits) is answered 413 before anything is dispatched; the internal protocol from a peer on the same host is not capped. -
Security:
rust/hostdependencies. wasmtime and wasmtime-wasi 49.0.1 to 49.0.4 (RUSTSEC-2026-0321 to 0327, 0352 to 0364, 0378, 0379 among them the aarch64br_tablemiscompile and a filesystem sandbox escape), rustls 0.23.45 to 0.23.46 (0346 to 0349), rustls-webpki 0.103.16 (0382), webpki-roots 1.0.11 (0381), quinn-proto 0.11.19 (0338 to 0342), ctutils 0.4.5 (0385), tokio 1.54.1 (0386), and the yanked chacha20 0.10.1 to 0.10.2. The unmaintainedrustls-pemfileis gone: the RDS bundle is read withrustls::pki_types's own PEM reader.cargo auditonrust/hostandrust/reports nothing.