Skip to content

ESS 0.60.0

Choose a tag to compare

@b10x-bot b10x-bot released this 15 Oct 01:42
· 1009 commits to main since this release
19d6010

Added

  • Source format ess/25. Its first construct: a create-or-update command selects among its update
    branches with when_subject: predicates over the stored row's fields and held state, beside an
    unknown_instance: branch that creates: the row, as when_subject_state: already allowed. The
    creating branch answers an identity no row carries before any predicate reads a row, and the
    predicates must partition the stored rows alone (a text field compared in them still needs a
    default branch, as on any command). Synthesis writes the creation on an identity nothing stores
    and one scenario per stored-row branch on an arranged row; the interpreted target and the
    generated Rust and Go behaviour pass them, and Entity Runtime lowering refuses the command by
    name (ExistenceSelectionUnsupported). Below ess/25 the pairing keeps its
    conflicting_declaration refusal, now with a hint naming ess/25; a model that does not use it
    keeps its IR bytes and digest (#530,
    docs/design/cross-record-and-stored-field-guards.md).

  • From ess/25, a command guard compares a text fact with its surrounding ASCII whitespace
    removed: trimmed: wraps one of equals, in, equals_ignore_case, in_ignore_case,
    starts_with, ends_with and contains, so not: {reason: {trimmed: {in_ignore_case: [NEW, HOLD, DONE]}}} no longer claims that DONE is refused when the implementation trims before
    it checks. Only space, tab, LF, VT, FF and CR are removed, at either end, so Rust, Go and
    TypeScript answer alike and a Unicode space compares as itself. It is admitted in when:,
    when_subject: and when_related: and refused elsewhere as unsupported_construct, and below
    ess/25 by format alone, naming ess/25. A literal that holds for no text once the fact is
    trimmed is refused as empty_declaration, as an empty in is: an equals or in literal,
    case-folded or not, with ASCII whitespace at either end, a starts_with literal with it at its
    start and an ends_with literal with it at its end; contains is not affected. Synthesis sends
    the guarded branch the literal padded with ASCII whitespace, and the other side the literal with
    an inner space and between two U+00A0, also inside a forall or exists body, so a target
    that does not trim, or trims Unicode spaces, fails a scenario. Entity Runtime lowering
    refuses it as TrimmedTextUnsupported, infra-spec/1 and every suite admission refuse it by
    name, and no suite format changes (#532,
    docs/design/value-expressions.md § E10).

  • ess/25: an outcome with instances: may publish an event once per row it changed, listed in
    emits: as {event: <name>, each: changed, when_state: <states>}; its payload reads that row as
    it was before the outcome with {changed: <field>} and {changed: state}, and when_state:
    publishes it only for rows that held one of those states. The interpreter publishes one occurrence
    per admitted row, in ascending order of identity; synthesis claims each with the row's values,
    and a segment of rows the filter leaves out claims none. Entity Runtime and the Rust, Go, Web and
    Clap synthesizers keep refusing set effects by name, the generated explorers leave such an outcome
    out, and outcome-set-effect-changed names each per-row event and its filter. Below ess/25, or
    on an outcome without instances:, the entry is refused at its place in emits:; a model that
    does not use it keeps its IR bytes and digest (#531,
    docs/design/set-effects-over-filtered-instances.md).

  • before_row: true on an external: refusal (ess/25): the refusal answers at step 2 of the
    precedence order, among the input-guarded refusals in declaration order, before existence and
    the held state, for a check the implementation makes before it reads the row (a credential, a
    client authenticated before the token it names is looked up). The held-state branches declared
    after it validate, where the conflicting_declaration of #486 refused them; without
    the key that refusal is unchanged. The key is refused (conflicting_declaration) on a branch
    that is not external:, on one naming no error:, beside compensates: true, where the error
    payload reads a row, declared after a branch a later step answers, and on a command declaring a
    when_related: branch; below ess/25 it is refused naming ess/25. Synthesis forces the
    refusal on a row the held state would also select and once more on an identity no row carries,
    beside existing_instance: on an identity a row already carries, and on an input each input
    refusal declared after it selects; the interpreted target, the generated Rust and Go behaviour and the Go and TypeScript explorers
    answer it there, and Entity Runtime lowering refuses it by name (OutcomeShapeUnsupported).
    ess verify diff reports the key gained or lost as outcome-condition-changed. A model without
    the key keeps its IR bytes and digest (#534,
    docs/design/cross-record-and-stored-field-guards.md).

  • absent: {row: {<field>: <literal>…}} on a view (ess/25): a view whose filter is the one
    comparison <identity> == param.<name> declares its answer for an identity no row carries, one
    default row holding the requested identity, each literal of row: (typed as a sets: literal
    is, inside the alphabets and invariants of its newtypes) and nothing in the Optional fields it
    leaves out. Refused as conflicting_declaration on any other filter, on an Optional identity
    parameter, beside an aggregate or paging:, and on the identity field in row:; as
    missing_declaration where a required projected field has no value; as undeclared_reference
    for a field the view does not project; and below ess/25 as unsupported_format_version naming
    ess/25. An error: answer is not in this cut. The IR carries absent: {param, row} on the
    view, each Integer or Decimal literal held by value (1.50 as 1.5), omitted where absent,
    so other models keep their IR, suites and artifacts. The interpreted target answers the default
    row; synthesis reads the view for an identity no scenario stores beside each stored-row read and
    requires contains the identity, every literal and null in each Optional field row: leaves
    out, with no suite format change. Generated view queries stay owed, naming absent:; OpenAPI
    states the default row in the read's description. ess verify diff writes ess-diff/19 for
    view/<name>/absent-changed, breaking for readers. The ess-http/1 binding is unchanged:
    single still requires empty, which a read of an unknown identity no longer reaches on such a
    view (#533, docs/design/read-api-view-idioms.md).

  • An ess-http/1 route declares how a command's request is encoded:
    request: {encoding: json | form | query, unknown: refuse | ignore}. form is an
    application/x-www-form-urlencoded body, and query carries every input field the path does
    not map in the query string, so a command on get no longer has to map its whole input to the
    path (ESS-HTTP-014 admits a query-encoded field). ess specify http validate and compile
    read the key and write it into the route of ess-http-ir/1, omitting encoding: json,
    unknown: refuse and a request holding only those. ESS-HTTP-020 refuses an encoding outside
    the set, json or form written on a get command, query on any other method, an unknown
    outside the set, ignore beside json, and request on a view; ESS-HTTP-021 refuses a
    form or query field whose type has no flat spelling (a struct, a union, a map, Json, or an
    Optional or List of anything but a scalar), naming the field and its type. A refining
    document restates each refined route's request (ESS-HTTP-012). A binding that uses no
    request compiles to the bytes 0.60.0 wrote, and the format stays ess-http/1. The OpenAPI
    projection reads it (below); the generated servers do not yet
    (#493, docs/design/http-presentation-binding.md,
    "Request encoding").

  • An ess-http/1 route declares the status and body of each outcome:
    outcomes: {<outcome>: {status, body}}, the body envelope (the default), response (the
    command's response, bare, for a returns: true outcome without an error), none or
    {<member>: error} (the error's wire name under <member>, beside the error's fields). A view
    route declares body: single with empty: {status} to answer its one row, and top-level
    refusals: overrides the standard malformed, not_granted and unfinished answers with a
    status and a literal body or none. ess specify http compile writes them into ess-http-ir/1,
    omitting envelope, rows and an empty outcomes or refusals, so a binding without them keeps
    the bytes 0.60.0 wrote. ESS-HTTP-022 refuses an outcome the command does not declare and
    outcomes on a view; -023 a status outside 100..=599, a 1xx, a 2xx on an outcome with an
    error, a 4xx or 5xx on one without, and a 3xx other than 301, 302, 303, 307 and
    308; -024 a body the outcome or route does not admit (response without returns: true, the
    error object without an error or under a member one of the error's fields travels as, a body on
    204, single on a command or a paged view); -025 two outcomes on one status their bodies
    cannot tell apart, an unlisted outcome counted at its derived status with the envelope; -029 a
    refusal key outside the set, a status outside its class or a body other than a literal or none;
    -030 single without empty, empty without single or an empty status outside 4xx;
    -031 single on a view whose filter does not select its source entity by identity. A refining
    document restates each refined route's answers and refusals: (ESS-HTTP-012). The status ESS
    derives per outcome moved from ess_gen::http into ess_http::status, re-exported unchanged, so
    a binding is checked against the same answer the document and the servers write. The OpenAPI
    projection reads them (below); the generated servers do not yet
    (#493, docs/design/http-presentation-binding.md,
    "Response body per outcome", "Status per outcome", "Standard refusals").

  • Composition format ess-composition/4 adds roles:. An entry says that the component one
    imported service selects plays the component another selects (service: and plays:, both
    service keys of the composition), and which local command stands where in that contract:
    commands: [{local, stands, contract}], where stands is implements (the local command answers
    the contract command: every contract input field is a local input field of a conforming type,
    compared as type conformance compares fields, and a local input the contract does not declare is
    Optional and not null_when_absent) or gates (the local command decides before the contract
    command succeeds; recorded, not executed, and no inputs are compared). ess specify compose
    admits each command on its component's accepts surface and writes the roles, in document order,
    into the compiled composition, each end with the system, version, digest and component it was
    checked at. New refusals: unknown_role_service (roles[i].service or roles[i].plays is not
    imported), conflicting_role (a role playing its own service, a repeated role or command entry)
    and role_input_drift (an implements entry whose inputs differ, naming every field); a command
    outside its selected component or undeclared keeps reference_outside_component or
    unresolved_semantic_reference, its detail naming roles[i].commands[j].local or .contract.
    /1 to /3 documents keep their compiled bytes, and one carrying roles, even empty, is still
    refused as an unknown field; the client plan does not change. Delivering a local event through
    the contract's delivery is not part of it (#525,
    docs/design/composition-contract-roles.md).

  • ess verify conform mutate scores a precedence-swap of two branches equivalent
    (ESS-MUTATE-005) when every input both branches select is already taken by branches answered
    before both, instead of survived: the two then never answer the same request, so their order
    decides nothing. Only branches selected by an input guard alone are subtracted, decided as a dead
    guard is; a branch the held state or a related row also decides is not, and a swap whose overlap
    is partly uncovered keeps the verdict its run gives. Only two branches addressing the same
    stored row through the same input field, or neither a stored row, are scored so: on a row-set
    command their order otherwise decides which row a request neither of them takes reads. The
    entry names those branches in a new overlap_taken_by list, apart from unsatisfiable_guard,
    which still says only that the two guards never overlap, and its text line ends
    — ESS-MUTATE-005: every input both branches select is taken first by `done-paused-default` .
    A report or manifest carrying it is the new
    ess-mutation-report/6 or ess-mutation-manifest/6, and one carrying none keeps its earlier
    version and bytes; an earlier manifest carrying it, /5 included, or one carrying it on a mutant
    that is not a precedence-swap, is refused (#539).

  • An ess-http/1 outcome declares response headers and a redirect, and a standard refusal
    declares headers. headers: {<Name>: …} maps an RFC 9110 token to {literal: <text>},
    {source: <path>} (input.<field>, response.<field>, error.<field> or error.wire, a
    scalar or an Optional scalar) or {challenge: {scheme, params}}, one WWW-Authenticate
    challenge whose params are literals or sources, so a 401 answers Bearer with
    error: {source: error.wire} (RFC 6750 §3). redirect: {location, mode: query | fragment, parameters} sends the caller to the URI a String source holds, with each parameter a scalar
    source. ess specify http compile writes both into ess-http-ir/1, header, param and parameter
    maps in name order whatever the authored order, each name as authored, and omits an empty map,
    so a binding without them keeps its bytes. ESS-HTTP-026 refuses a header name that is not a
    token, Connection, Content-Type, Content-Encoding, Content-Length, Transfer-Encoding
    and Location in any case, a name declared twice ignoring case, a literal that is not a field
    value, a header reading error.wire whose wire name, known when the binding compiles, is not
    one, a source naming no field, a non-scalar or one the outcome cannot supply, error.wire
    beside an error field named wire, and any source in a refusal's header; -027 a challenge
    scheme or param name that is not a token, a param twice, a literal param or one reading
    error.wire whose text a quoted string cannot carry, a challenge under any other header, and a
    401 outcome or refusal without a WWW-Authenticate challenge; -028 a redirect on a status
    other than 301, 302, 303, 307 and 308, one of those without a redirect, a
    location that is not a String or a newtype of one, is Optional or reads error.wire, a
    mode absent or outside the set, a parameter name outside RFC 3986 unreserved, and a
    parameter twice or reading what a header could not. -024 refuses a body other than none
    beside a redirect. Two redirects on one status, both body: none, are told apart when one
    always writes a parameter (its source not Optional) the other never writes, so an OAuth
    authorization endpoint answers success (code) and error (error) both with 302 (RFC 6749
    §4.1.2, §4.1.2.1), and when both write one parameter from error.wire and their error wire
    names differ, so access_denied and invalid_scope both answer 302 with their own error
    (§4.1.2.1); ESS-HTTP-025 refuses two that neither tells apart. A refining document restates
    each refined outcome's headers and redirect and each refusal's headers (ESS-HTTP-012).
    The OpenAPI projection reads them (below); the generated servers do not yet (#493,
    docs/design/http-presentation-binding.md, "Response headers", "Redirects").

  • ess generate --kind openapi --http projects a binding's stage-2 keys. A form route's request
    body is application/x-www-form-urlencoded; a query route has no request body and no
    {command}.Input, and each input field its path does not carry is an in: query parameter
    under its wire name, a List with style: form and explode: true. Each declared status is
    its own response: body: response answers the command's {command}.Result bare, {<member>: error} an object whose <member> is a const of the error's wire name beside the error's own
    fields, none no content; two error objects on one status are told apart by a discriminator on
    <member>. Declared headers are response headers (a literal a const, a source a string
    required unless it may be absent, a challenge a string described param by param), and a
    redirect answers a required Location header of format: uri and no content. A single view
    answers its row schema at 200 and no content at its empty status, and a refusals:
    override replaces the standard answer on every bound operation that can give it. Every response
    of a command whose route uses a stage-2 key, or of a binding that overrides a refusal, carries
    x-ess-outcomes, the outcomes it answers in declaration order, because a bare body names no
    branch. security and securitySchemes stay omitted. A binding without a stage-2 key keeps
    every byte the projection wrote before (#493,
    docs/design/http-presentation-binding.md, "What OpenAPI writes (k4)").

  • ess generate synthesize --target rust --http FILE [--refines FILE] emits a Rust server that
    serves an ess-http/1 binding. A bound route is a template: several methods share one path, a
    path variable is percent-decoded into its input field or view parameter, a path no route matches
    is 404 and one a route matches under another method is 405 naming every method there. A
    form request (Content-Type: application/x-www-form-urlencoded) and a query request are
    decoded by wire name, + as a space, a List repeating its key; a scalar key given twice, an
    empty non-String value, an unknown key under unknown: refuse, and a body or query naming a
    field the path carries are malformed. A listed outcome answers its declared status and body
    (response, {<member>: error}, none), its headers, its WWW-Authenticate challenge, each
    param a quoted string whose " and \ are escaped, and its redirect, whose parameters are
    form-encoded in name order onto the location's query or as its fragment, an absent optional
    left out; an outcome not listed keeps the envelope at its derived status. A sourced header,
    param or location that cannot be written (a control character, a location that is not an
    absolute URI without a fragment) is never written: the answer is unfinished with committed
    true. refusals: overrides malformed, not_granted and unfinished on the routes the
    binding names; every derived route keeps today's answers, and a single view answers its row
    bare or its empty status without a body. The served /openapi.json is the bound document. A
    binding with an open route, or a route a request for /openapi.json or /docs would match
    (/{page} included), is refused at synthesis naming the operation, and --http with any target
    but rust and go is refused by name with exit status 2. Without --http every synthesized
    byte is unchanged (#493,
    docs/design/http-presentation-binding.md, "What the servers write (k5, k6)").

  • ess generate synthesize --target go --http FILE [--refines FILE] emits a Go server that serves
    an ess-http/1 binding as the Rust server does: template dispatch over the escaped path, form
    and query decoding, each listed outcome's status, body, headers, challenge and redirect, the
    refusals: overrides, a 405 naming its methods in Allow, and a 204 written without a
    length or a body. Where the binding writes the answer, the two servers answer one request with
    the same status, headers and body bytes: a single row, a response body and an error object
    write their members in the order the model declares them and escape text as the Rust server
    does (U+2028 and U+2029 written as themselves), a json body is read as the Rust server reads
    one (text after the document, a lone surrogate escape or nesting past 64 is malformed, and of
    a repeated member the first is kept), routes match the path as the request spells it
    (/openapi%2Ejson is not /openapi.json, and a 404 names the escaped path), and any text a
    binding declares, such as a refusal literal holding a no-break space, is written into the Go
    source as a valid Go string. The same bindings are refused at synthesis, --http
    is now refused only for web and clap, and without --http every synthesized Go byte is
    unchanged (#493,
    docs/design/http-presentation-binding.md, "What the servers write (k5, k6)").

  • ess-http/1, a document beside the specification that binds its commands and views to HTTP
    routes, pinned to the specification's source digest as ess-transport/1 is: each route names
    one command or view, a method, and optionally a path template and the input field or view
    parameter each path variable reads. A route without a path is open, for a protocol that leaves
    the path to each server. ess specify http validate --path MODEL --binding FILE checks one
    against the specification, and ess specify http compile … --out FILE writes its canonical
    ess-http-ir/1, routes ordered by path, then method. Every refusal is reported at once with a
    code and the path of the key: ESS-HTTP-001 another type, -002 a specification key that
    disagrees, -004 a route naming no operation, both a command and a view, or one the
    specification does not declare, -005 one operation routed twice, -006 a method outside
    get, put, post, delete, options, head, patch, trace or not admitted for the
    operation (a view on get only; a command on post, put, patch, delete or get), -013
    a command no component accepts or a view no reached_by: network component serves, and -014
    a command on get with an input field the path does not map. Every object refuses an unknown
    key by name, so a key of a later stage of the format (request, outcomes, headers,
    redirect) is refused, not ignored. Path templates, parameter sources, route collisions,
    refines: and the OpenAPI projection are the entries below
    (#493, docs/design/http-presentation-binding.md).

  • ess-http/1 path templates, parameters and route collisions are checked. A path starts with
    /, has no empty, . or .. segment, no ?, # or %, literal segments of RFC 3986 path
    text, and variables only as whole segments {name}; ESS-HTTP-007 refuses each segment that
    breaks this, naming it. ESS-HTTP-008 refuses a path variable not mapped, mapped twice, or a
    mapping for no variable of the path, and one source read by two variables of a route, at the
    second; ESS-HTTP-009 a source outside input.<field> (command)
    or param.<name> (view), naming no field, or of a type a segment cannot carry (anything but
    String, Integer, an enum, or an unconstrained newtype of one of them). A command on get
    now counts an input field as mapped only when a variable of its path maps it (ESS-HTTP-014).
    Each component's surface, declared routes together with the routes ESS derives for every
    operation the binding does not name, is checked for two operations on one method and template
    whatever the variables are called (ESS-HTTP-010, "two operations claim the same method and
    path") and for a literal beside a variable where one request path matches both
    (ESS-HTTP-011; neither takes precedence), each naming both operations; a route refused for
    anything else still replaces its operation's derived route, and is checked whenever its method
    is admitted and its path parsed. In ess-http-ir/1 parameters are typed sources, and
    derived_routes records the derived routes the binding was checked against, ordered by
    component, path, method and operation. IR that breaks what compiling guarantees without the
    specification does not read back, naming the key: routes, derived_routes or parameters
    out of canonical order, an operation routed twice, a derived route for a routed operation, a
    derived path with a variable or outside the grammar, or parameters that disagree with their
    path (#493).

  • ess-http/1 refinement: a server binding names the protocol binding it fills in with
    refines: sha256:<hex>, the digest of that file's bytes, and ess specify http validate and
    compile take the file with --refines FILE. ESS-HTTP-003 refuses, at refines, a value
    that is no such digest, refines: without --refines, a refined file whose bytes have another
    digest (reformatting it is a deliberate re-pin), a refined binding that itself refines, and
    --refines for a document without refines:. ESS-HTTP-012 refuses a route that changes a
    method or a path the refined binding fixed, or leaves a fixed path open, naming routes[i],
    the refined value and the new one, and a refinement that drops a route of the refined binding.
    Filling in an open path and routing an operation the refined binding does not name are
    admitted. The refined binding is checked against the same specification, its refusals keyed
    under refined. (refined.routes[0].method). ess-http-ir/1 records the digest as refines,
    absent when the binding refines nothing, so existing IR keeps its bytes, and a reader refuses a
    refines that is not a sha256: digest of 64 lower-case hex digits
    (#493).

  • ess generate --kind openapi --http FILE [--refines FILE] projects an ess-http/1 binding:
    the binding is compiled as ess specify http compile compiles it, with the same refusals, and
    each operation it routes is served at the declared method and path, so one path may carry
    get, put, post, delete and patch. Each path variable is a required in: path
    parameter with the input field's or view parameter's schema; a command's field carried in the
    path leaves the request body ({command}.Input is the body, and is absent with no body when
    the path carries every field), and a view's parameter carried in the path leaves the query. A
    bound operation carries x-ess-route-source: binding, as a bound channel carries
    x-ess-address-source: transport. An operation the binding does not name keeps its derived
    route and bytes, and every projection generated without --http keeps its bytes. A binding
    with an open route is refused, naming each such operation, and writes nothing; --http with
    any --kind but openapi is refused by name, as --transport is without --kind asyncapi,
    and --refines requires --http. ess_gen::http::bound_routes is the route table with a
    binding (ess_gen::http::Binding), ess_gen::http::Method gains Delete, Patch and Put,
    and ess_gen::http::routes is unchanged
    (#493).

  • ess verify conform mutate mutates the predicate of a when_subject: guard and of a
    when_related: guard: guard-negate, guard-connective and guard-boundary (strictness swap,
    outward literal and equality flip) edit it as they edit a when:, and each mutant is compiled,
    synthesized and scored like the when: ones. Its id names the key after the outcome, such as
    guard-negate/<command>/<outcome>/when_subject or
    guard-boundary/<command>/<outcome>/when_related/0. A model had no mutant on either predicate,
    so a target that ignored such a guard went unnoticed by the audit
    (#515). A when: mutant keeps its id, and a model with
    neither guard keeps its report and manifest byte for byte; the report and manifest formats are
    unchanged. The {field, equals} form of when_subject: and a row set's where and forall
    are not mutated.

  • ess verify conform mutate says why of every sets: entry that has no sets-drop mutant.
    Where sets-drop is selected, --target and --emit print, after their own output and on
    stderr in every format, one note: sets-drop/<command>/<outcome>/<field> has no mutant (<reason>): … per such entry, the reason one of creates_row, source_not_input and
    optional_field. A model whose every write is on a creating branch got no sets-drop mutant and
    no word about why (#515); the entries are left out as
    before, because there a dropped write only leaves the value to the implementation, and are now
    listed. Which entries have a site is unchanged, and so are the report and manifest bytes and
    formats: the list is not persisted.

  • ess verify conform mutate --scenarios DIR --target interpreted runs authored ess-scenario/1
    scenarios in the audit. DIR is read and refused as ess verify conform run --scenarios reads
    it. Each scenario runs against the interpreter of the specification as part of the baseline, and
    against the interpreter of each mutant beside that mutant's synthesized suite. One that fails
    against a mutant's interpreter kills the mutant and is listed among its killers
    (<domain>/authored/<scenario>). The interpreters run on the scenario clock run --target interpreted uses, so a now guard is decided at the step's instant and a scenario run passes
    executes here too. One the baseline fails refuses the audit with ESS-MUTATE-001, and one the
    baseline executed that requires an outcome makes that outcome witnessed. One the baseline did not
    execute witnesses nothing and leaves a mutant nothing killed inconclusive (or unwitnessed),
    never survived. A precedence-swap whose branches answer alike stays equivalent: an authored
    scenario that fails against it differs only in the outcome's name. A mutant only an authored
    scenario pins, such as one on an outcome whose scenario synthesis refuses, was unwitnessed or
    survived because mutate ran no authored scenario (#515).
    The scenario-clock runner is public as ess_conformance::now_offset::scenario_clock_runner.
    --scenarios is refused with --target billing and --target oracle-fixture, which implement no
    mutant, and with --emit, --collect and --known-failing. Without --scenarios every report
    and manifest keeps its bytes; no persisted field is added and no format moves. The library entry
    point is ess_conformance::mutate::audit_with_authored.

Changed

  • A read-only command can declare unknown_instance:. A command whose branches only preserve the
    row its input names (preserves: with instance: from input), or refuse it through
    when_subject:, declares its answer for an identity no row carries, where validation refused the
    marker with unreachable_branch. Synthesis writes the scenario that sends such a command an
    identity nothing stores and expects the declared branch and its error; the interpreted target
    and the generated Rust and Go behaviour pass it. A command with no input-addressed branch keeps
    unreachable_branch, a creating unknown_instance: beside a preserves: branch alone is still
    refused conflicting_declaration, and a preserving command without the marker synthesizes the
    scenarios it did (#535,
    docs/design/typed-literals-and-unknown-instances.md section 2b).
  • A literal written through a newtype is refused as type_mismatch where it lies outside that
    newtype's alphabet: or makes one of its invariants: other than true, in sets:, payload:,
    a binding's mapping:, an enum variant's attributes: and a view's default row alike, with the
    sentence an input's example: gets for the same value. Before, only the representation was
    checked, so code: xyz over a newtype drawn from ab, or level: 0 over one whose invariant is
    value >= 1, validated and named a value no target could hold. A model whose literals are
    values of their types is unaffected (#533).

Fixed

  • The disclosure scenarios synthesized for a one_time_response: outcome send every command as
    the suite's caller, as the outcome's own scenario does, wherever a command of the model reads
    its caller ({caller: <attribute>}). Before, they sent no caller, so an outcome recording who
    issued its secret (sets: {issued_by: {caller: user_id}}) left the interpreted target with no
    value to record, and every such scenario came back unsupported instead of testing the
    non-disclosure rule. A model none of whose commands reads its caller keeps its suite bytes
    (#550).

  • The generated explorers (Go Explore and the TypeScript explore) draw a command with a
    preserves: outcome on a supplied subject, instead of leaving the whole command out of every
    sequence. The model leaves the row as it was, state and fields, so a target that changes it on
    the accepted no-op answer (pause when already paused, logout when logged out) fails at the view
    that shows the change, naming the step. Concurrent exploration still leaves such a command out
    with the reason it gave before, and a deletes: outcome stays excluded as before
    (#536).

  • The generated explorers' model applies a selected-rows effect (moves: or updates: with
    instances: {where: …}) to every row the filter selects, as synthesis and the interpreter do:
    a moves: takes each selected row resting in the transition's from states and skips the
    others, and the outcome's sets: apply to every changed row. Before, the model kept the
    selected rows as they were, so the first view read after such a step reported a disagreement a
    correct target did not make. As in the interpreter, a row outside a moves:'s from states is
    skipped before its filter is read, and an Optional field no outcome wrote is read as absent,
    so the row is not selected; a filter reading a field whose value the model does not hold (a
    required field no outcome wrote) is reported in undetermined and the draw redrawn. A bulk
    deletes: with instances: is now excluded by name, as a single deletes: is, and so is an
    outcome with an affects: entry, which the model does not apply: such a command was drawn
    before and a correct target failed with a view disagreement on the rows affects: wrote. The
    sequential explorer also draws a Decimal input as it
    draws an Integer one, instead of excluding the command ("is a decimal")
    (#538).

  • The generated sequential explorers stop sending a command the target answers ErrUnsupported
    (TypeScript: throws unsupported) only in the held state of the row that step addressed (a step
    that addresses no row is the state (no row)), and keep drawing it in every other state, instead
    of excluding the command for the rest of the run. A target that could not drive a command in one
    state lost the command and every state reachable only through it. A command refused in every
    state it was drawn in, and driven in none, is in excluded with the reason it had before, so
    allowExcluded and the coverage check read as they did. A command driven elsewhere is reported in
    a new, additive result field refused (Go Refused []Refusal), one {command, state, reason, count} per refused state, absent when empty so a result without one keeps its bytes; an outcome
    no step took that a refused step would have taken is in excludedOutcomes. The strict assertion
    (assertExplored without allowExcluded) fails on a nonempty refused, as it failed on the
    exclusion each refusal made before, and allowExcluded accepts it; the message lists each refusal
    on its own line (command, held state, reason, steps not sent) under its own heading, never among
    the exclusions, and marks an excluded outcome only a refused step would have taken. A view the
    target refuses stays excluded for the run, and concurrent exploration keeps its per-command
    exclusion. A seed now names a different sequence wherever a command was refused, because the
    command stays in the draw
    (#537).

  • An ess-composition/1 document carrying conformances with a null value (conformances:,
    conformances: ~, JSON null) is refused as unsupported_format, as conformances: [] is,
    instead of reading as a document without the key and compiling. /2 and later read a null
    conformances as the absent key, as before (#525).

  • No ESS crate depends on serde_yaml or unsafe-libyaml any more, so cargo deny check advisories in a crate that depends on ESS no longer reports RUSTSEC-2024-0449. YAML now goes
    through one new crate, ess-yaml (crates/specify/ess-yaml), which every other crate names
    instead of a YAML library (the infra-* crates through ess_primitives::yaml). Its backend is
    serde_norway 0.9.42, a stopgap: a maintained fork of serde_yaml 0.9 that reads every
    committed document to the same values and writes the same bytes, so no accepted document,
    diagnostic or generated byte changes. An ESS-owned parser and emitter are planned behind the
    same API (docs/design/yaml-parser-replacement.md,
    #541).

  • ess generate synthesize --target go emits a module that builds when bounded contexts reference
    each other in a loop, for example a command outcome in publish refusing with an error declared
    in session, session referencing subscription, and subscription referencing a type of
    publish. Each bounded context had its own package under types/, so those packages imported
    each other and go build failed with import cycle not allowed. The bounded contexts of one loop
    now share one package, named by their package names joined (types/publishsessionsubscription/),
    with one file per bounded context and a file of its own carrying the package's documentation
    and the one Unimplemented stub their owed seams are methods of. A declared name two of them
    share is repaired with _, as within one bounded context. Every bounded context outside a loop
    keeps its own package, and a model whose references form no loop keeps its Go output byte for
    byte.

  • ess verify conform mutate no longer reports a precedence-swap equivalent when a binding's
    failure policy treats its two refusals differently. Two branches refusing with the same error
    and no payload were called an identical answer (identical_answer, ESS-MUTATE-005) even
    where a binding invoking the command dropped one and escalated the other, or ended a bounded
    retry on one alone (final), so the swap changed what the binding published and still scored
    equivalent, and with --scenarios an authored scenario that saw the escalation killed
    nothing. Such a swap now carries no identical_answer and is scored as any other
    (#515). A model whose bindings answer both refusals
    alike, or that has no binding on the command, keeps its report byte for byte.