Skip to content

hecks 6.0.0

Choose a tag to compare

@github-actions github-actions released this 28 Oct 23:28
· 852 commits to main since this release
  • AwsBox no longer runs a tunnel, and a world that declares tunnel is refused. A box is reached through its CDN or load balancer and Caddy with an origin-secret header (ADR 0156). The cloudflared service, its outbound rule, its token secret and its post-roll check are gone from the generated stack; the message a refused world gets names cdn and origin_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-rails renders 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 with member rows of a value object named App (title, home) or Listing (aggregate, label, columns) in its own bluebook; the new RailsApp bluebook (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_app projection 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 as dev-<role>, tells Governance that actor holds the role, and answers 404 anywhere else (Hecks::Rails.dev_actor = true or false overrides the environment).

  • Dispatcher#state_admits? answers whether a record's state lets a command run, before any argument is known. validate stops at the arguments stage, so a command with an argument still missing never reached the lifecycle from: 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 how hecks-rails offers 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_for carries the forgery token, and a refused command's errors stay on its own form. The form had no authenticity_token field (the helper it asked for is private), so a browser post failed with InvalidAuthenticityToken whenever protection was on; it now adds the token when protection is on. A new errors: 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 pattern is translated for the browser (Hecks::Forms::HtmlPattern). A browser reads the attribute as JavaScript, in its v mode, 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, \Z and \h are 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 without over: now says it is the bulk form and that the one-record form is called on the record's handle, instead of missing keyword: :over. An adapter's constructor may name registry: and aggregate:: 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 .bluebook now loads wherever a Ruby one does (attaches, framework members), not only from a project folder; it still needs hecks-parse built.

  • 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.bluebook adds Seal (the head of a SHA-256 chain over a journal's rows through an ordinal, and the head before it) and Audit (Verify walks the chain from the seals and ends verified or unproven, naming the row or the reason). Seals live beside the journal they cover: an anchors file next to a Heki journal, a hecks_ledger_anchors table 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 leaves HECKS_SEAL_EVERY or more lineage rows past the last seal (a thousand unless set; 0 turns it off) seals in the same transaction as the rows it covers, so a seal and its rows land together or not at all; the ledger seal <Domain> and ledger verify <Domain> tools seal the tail and walk the chain by hand, and exit non-zero when it cannot be shown intact.

  • A .bluebook can be written as plain sentences, and its examples run (ADR 0131). A file whose first line is Domain: <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 new Sentence chapter, projected into rust/parser/src/phrases.rs by hecks language_run.project_phrase_table. hecks-parse gains render (the Ruby text for a chapter, which Ruby loads through Hecks::SentenceForm), format (any chapter as canonical sentences), complete (the sentences legal at a cursor) and examples; a refusal now carries a column (file:line:column: message) and the nearest phrases, and hecks-lsp starts its squiggle there and offers completion. A *.examples file 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 .behaviors and run by hecks run_behaviors; .behaviors tests gain as "<role>" and setup …, as: "<role>", which bind a caller for the dispatch (a dispatch with no as still binds none). A command may be run by several roles (role "Editor", "Admin" in the language, Who: Editor or Admin in sentences): the IR gains roles only 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 gains list.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-parse and the kernel. hecks-parse is not shipped in the gem, so a sentence chapter needs it built (HECKS_PARSE_BIN, rust/parser/target/, or the PATH). The sentence form covers the whole Ruby form except a few refusals it names (docs/sentence-form.md lists 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, and spec/sentence_coverage_spec.rb holds the whole corpus to an exact read-back. An expression may be written inside a rule as this is true: <expression>. See docs/sentence-form.md.

  • hecks-parse reads a pattern in single quotes and a regexp literal. A {11} in pattern: '^[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.json carries them in one provides map (ADR 0119). The language keeps the contracts Hecks itself reads (authorization, membership, identity, privacy, subject_keys). Any other provides name 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, their Registry#*_provider_for lookups (use vendored_provider_for(domain, capability)) and their Exporter methods (use Exporter.provides). In ir.json the top-level newsletter, newsletter_issues, payments, registrations, payment_connection and checkout keys become provides.<name>, without the derived aggregate fields; an attribute key exports as { "attribute", "default" }. rust/host's commerce readers and hecks-build read and write the new shape, so a host must read an ir.json from the same release. The grammar's provides takes any text-valued key through one open row, and the Rust parser accepts it. Package bluebooks need no change.

  • Hecks::Ports::Authentication and Hecks::Adapters::GoogleAuthentication are 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 .adapter and .port declarations, the oauth2 and google-id-token test gems and spec/ports/authentication_spec.rb return 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_runner stops 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/host dependencies. wasmtime and wasmtime-wasi 49.0.1 to 49.0.4 (RUSTSEC-2026-0321 to 0327, 0352 to 0364, 0378, 0379 among them the aarch64 br_table miscompile 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 unmaintained rustls-pemfile is gone: the RDS bundle is read with rustls::pki_types's own PEM reader. cargo audit on rust/host and rust/ reports nothing.