Repository navigation
ESS 0.60.0
Added
-
Source format
ess/25. Its first construct: a create-or-update command selects among its update
branches withwhen_subject:predicates over the stored row's fields and held state, beside an
unknown_instance:branch thatcreates:the row, aswhen_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). Belowess/25the pairing keeps its
conflicting_declarationrefusal, now with a hint namingess/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 ofequals,in,equals_ignore_case,in_ignore_case,
starts_with,ends_withandcontains, sonot: {reason: {trimmed: {in_ignore_case: [NEW, HOLD, DONE]}}}no longer claims thatDONEis 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 inwhen:,
when_subject:andwhen_related:and refused elsewhere asunsupported_construct, and below
ess/25by format alone, namingess/25. A literal that holds for no text once the fact is
trimmed is refused asempty_declaration, as an emptyinis: anequalsorinliteral,
case-folded or not, with ASCII whitespace at either end, astarts_withliteral with it at its
start and anends_withliteral with it at its end;containsis 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 aforallorexistsbody, so a target
that does not trim, or trims Unicode spaces, fails a scenario. Entity Runtime lowering
refuses it asTrimmedTextUnsupported,infra-spec/1and every suite admission refuse it by
name, and no suite format changes (#532,
docs/design/value-expressions.md§ E10). -
ess/25: an outcome withinstances: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}, andwhen_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, andoutcome-set-effect-changednames each per-row event and its filter. Belowess/25, or
on an outcome withoutinstances:, the entry is refused at its place inemits:; a model that
does not use it keeps its IR bytes and digest (#531,
docs/design/set-effects-over-filtered-instances.md). -
before_row: trueon anexternal: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 theconflicting_declarationof #486 refused them; without
the key that refusal is unchanged. The key is refused (conflicting_declaration) on a branch
that is notexternal:, on one naming noerror:, besidecompensates: 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; belowess/25it is refused namingess/25. Synthesis forces the
refusal on a row the held state would also select and once more on an identity no row carries,
besideexisting_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 diffreports the key gained or lost asoutcome-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 ofrow:(typed as asets:literal
is, inside the alphabets and invariants of its newtypes) and nothing in theOptionalfields it
leaves out. Refused asconflicting_declarationon any other filter, on anOptionalidentity
parameter, beside an aggregate orpaging:, and on the identity field inrow:; as
missing_declarationwhere a required projected field has no value; asundeclared_reference
for a field the view does not project; and belowess/25asunsupported_format_versionnaming
ess/25. Anerror:answer is not in this cut. The IR carriesabsent: {param, row}on the
view, eachIntegerorDecimalliteral held by value (1.50as1.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
requirescontainsthe identity, every literal andnullin eachOptionalfieldrow:leaves
out, with no suite format change. Generated view queries stay owed, namingabsent:; OpenAPI
states the default row in the read's description.ess verify diffwritesess-diff/19for
view/<name>/absent-changed, breaking for readers. Theess-http/1binding is unchanged:
singlestill requiresempty, 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/1route declares how a command's request is encoded:
request: {encoding: json | form | query, unknown: refuse | ignore}.formis an
application/x-www-form-urlencodedbody, andquerycarries every input field the path does
not map in the query string, so a command ongetno longer has to map its whole input to the
path (ESS-HTTP-014admits a query-encoded field).ess specify http validateandcompile
read the key and write it into the route ofess-http-ir/1, omittingencoding: json,
unknown: refuseand arequestholding only those.ESS-HTTP-020refuses an encoding outside
the set,jsonorformwritten on agetcommand,queryon any other method, anunknown
outside the set,ignorebesidejson, andrequeston a view;ESS-HTTP-021refuses a
formorqueryfield whose type has no flat spelling (a struct, a union, a map,Json, or an
OptionalorListof anything but a scalar), naming the field and its type. A refining
document restates each refined route'srequest(ESS-HTTP-012). A binding that uses no
requestcompiles to the bytes 0.60.0 wrote, and the format staysess-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/1route declares the status and body of each outcome:
outcomes: {<outcome>: {status, body}}, the bodyenvelope(the default),response(the
command's response, bare, for areturns: trueoutcome without an error),noneor
{<member>: error}(the error's wire name under<member>, beside the error's fields). A view
route declaresbody: singlewithempty: {status}to answer its one row, and top-level
refusals:overrides the standardmalformed,not_grantedandunfinishedanswers with a
status and a literal body or none.ess specify http compilewrites them intoess-http-ir/1,
omittingenvelope,rowsand an emptyoutcomesorrefusals, so a binding without them keeps
the bytes 0.60.0 wrote.ESS-HTTP-022refuses an outcome the command does not declare and
outcomeson a view;-023a status outside100..=599, a1xx, a2xxon an outcome with an
error, a4xxor5xxon one without, and a3xxother than301,302,303,307and
308;-024a body the outcome or route does not admit (responsewithoutreturns: true, the
error object without an error or under a member one of the error's fields travels as, a body on
204,singleon a command or a paged view);-025two outcomes on one status their bodies
cannot tell apart, an unlisted outcome counted at its derived status with the envelope;-029a
refusal key outside the set, a status outside its class or a body other than a literal or none;
-030singlewithoutempty,emptywithoutsingleor anemptystatus outside4xx;
-031singleon a view whose filter does not select its source entity by identity. A refining
document restates each refined route's answers andrefusals:(ESS-HTTP-012). The status ESS
derives per outcome moved fromess_gen::httpintoess_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/4addsroles:. An entry says that the component one
imported service selects plays the component another selects (service:andplays:, both
service keys of the composition), and which local command stands where in that contract:
commands: [{local, stands, contract}], wherestandsisimplements(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
Optionaland notnull_when_absent) orgates(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'sacceptssurface 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].serviceorroles[i].playsis not
imported),conflicting_role(a role playing its own service, a repeated role or command entry)
androle_input_drift(animplementsentry whose inputs differ, naming every field); a command
outside its selected component or undeclared keepsreference_outside_componentor
unresolved_semantic_reference, its detail namingroles[i].commands[j].localor.contract.
/1to/3documents keep their compiled bytes, and one carryingroles, 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 mutatescores aprecedence-swapof two branchesequivalent
(ESS-MUTATE-005) when every input both branches select is already taken by branches answered
before both, instead ofsurvived: 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 newoverlap_taken_bylist, apart fromunsatisfiable_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/6oress-mutation-manifest/6, and one carrying none keeps its earlier
version and bytes; an earlier manifest carrying it,/5included, or one carrying it on a mutant
that is not aprecedence-swap, is refused (#539). -
An
ess-http/1outcome 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>orerror.wire, a
scalar or anOptionalscalar) or{challenge: {scheme, params}}, oneWWW-Authenticate
challenge whose params are literals or sources, so a401answersBearerwith
error: {source: error.wire}(RFC 6750 §3).redirect: {location, mode: query | fragment, parameters}sends the caller to the URI aStringsource holds, with each parameter a scalar
source.ess specify http compilewrites both intoess-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-026refuses a header name that is not a
token,Connection,Content-Type,Content-Encoding,Content-Length,Transfer-Encoding
andLocationin any case, a name declared twice ignoring case, a literal that is not a field
value, a header readingerror.wirewhose 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 namedwire, and any source in a refusal's header;-027a challenge
scheme or param name that is not a token, a param twice, a literal param or one reading
error.wirewhose text a quoted string cannot carry, a challenge under any other header, and a
401outcome or refusal without aWWW-Authenticatechallenge;-028aredirecton a status
other than301,302,303,307and308, one of those without aredirect, a
locationthat is not aStringor a newtype of one, isOptionalor readserror.wire, a
modeabsent or outside the set, a parameter name outside RFC 3986unreserved, and a
parameter twice or reading what a header could not.-024refuses a body other thannone
beside a redirect. Two redirects on one status, bothbody: none, are told apart when one
always writes a parameter (its source notOptional) the other never writes, so an OAuth
authorization endpoint answers success (code) and error (error) both with302(RFC 6749
§4.1.2, §4.1.2.1), and when both write one parameter fromerror.wireand their error wire
names differ, soaccess_deniedandinvalid_scopeboth answer302with their ownerror
(§4.1.2.1);ESS-HTTP-025refuses 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 --httpprojects a binding's stage-2 keys. Aformroute's request
body isapplication/x-www-form-urlencoded; aqueryroute has no request body and no
{command}.Input, and each input field its path does not carry is anin: queryparameter
under its wire name, aListwithstyle: formandexplode: true. Each declared status is
its own response:body: responseanswers the command's{command}.Resultbare,{<member>: error}an object whose<member>is aconstof the error's wire name beside the error's own
fields,noneno content; two error objects on one status are told apart by a discriminator on
<member>. Declared headers are responseheaders(a literal aconst, a source astring
required unless it may be absent, a challenge astringdescribed param by param), and a
redirect answers a requiredLocationheader offormat: uriand no content. Asingleview
answers its row schema at200and no content at itsemptystatus, and arefusals:
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.securityandsecuritySchemesstay 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 aness-http/1binding. 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
is404and one a route matches under another method is405naming every method there. A
formrequest (Content-Type: application/x-www-form-urlencoded) and aqueryrequest are
decoded by wire name,+as a space, aListrepeating its key; a scalar key given twice, an
empty non-Stringvalue, an unknown key underunknown: 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, itsWWW-Authenticatechallenge, 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 isunfinishedwithcommitted
true.refusals:overridesmalformed,not_grantedandunfinishedon the routes the
binding names; every derived route keeps today's answers, and asingleview answers its row
bare or itsemptystatus without a body. The served/openapi.jsonis the bound document. A
binding with an open route, or a route a request for/openapi.jsonor/docswould match
(/{page}included), is refused at synthesis naming the operation, and--httpwith any target
butrustandgois refused by name with exit status 2. Without--httpevery 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
aness-http/1binding as the Rust server does: template dispatch over the escaped path,form
andquerydecoding, each listed outcome's status, body, headers, challenge and redirect, the
refusals:overrides, a405naming its methods inAllow, and a204written 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: asinglerow, aresponsebody 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), ajsonbody is read as the Rust server reads
one (text after the document, a lone surrogate escape or nesting past 64 ismalformed, and of
a repeated member the first is kept), routes match the path as the request spells it
(/openapi%2Ejsonis not/openapi.json, and a404names 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 forwebandclap, and without--httpevery 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 asess-transport/1is: 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 FILEchecks one
against the specification, andess specify http compile … --out FILEwrites 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-001anothertype,-002aspecificationkey that
disagrees,-004a route naming no operation, both a command and a view, or one the
specification does not declare,-005one operation routed twice,-006a method outside
get,put,post,delete,options,head,patch,traceor not admitted for the
operation (a view ongetonly; a command onpost,put,patch,deleteorget),-013
a command no component accepts or a view noreached_by: networkcomponent serves, and-014
a command ongetwith 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/1path 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-007refuses each segment that
breaks this, naming it.ESS-HTTP-008refuses 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-009a source outsideinput.<field>(command)
orparam.<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 onget
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. Iness-http-ir/1parameters are typed sources, and
derived_routesrecords 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_routesorparameters
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/1refinement: a server binding names the protocol binding it fills in with
refines: sha256:<hex>, the digest of that file's bytes, andess specify http validateand
compiletake the file with--refines FILE.ESS-HTTP-003refuses, atrefines, 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
--refinesfor a document withoutrefines:.ESS-HTTP-012refuses a route that changes a
method or a path the refined binding fixed, or leaves a fixed path open, namingroutes[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
underrefined.(refined.routes[0].method).ess-http-ir/1records the digest asrefines,
absent when the binding refines nothing, so existing IR keeps its bytes, and a reader refuses a
refinesthat is not asha256:digest of 64 lower-case hex digits
(#493). -
ess generate --kind openapi --http FILE [--refines FILE]projects aness-http/1binding:
the binding is compiled asess specify http compilecompiles 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,deleteandpatch. Each path variable is a requiredin: 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}.Inputis 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 carriesx-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--httpkeeps its bytes. A binding
with an open route is refused, naming each such operation, and writes nothing;--httpwith
any--kindbutopenapiis refused by name, as--transportis without--kind asyncapi,
and--refinesrequires--http.ess_gen::http::bound_routesis the route table with a
binding (ess_gen::http::Binding),ess_gen::http::MethodgainsDelete,PatchandPut,
andess_gen::http::routesis unchanged
(#493). -
ess verify conform mutatemutates the predicate of awhen_subject:guard and of a
when_related:guard:guard-negate,guard-connectiveandguard-boundary(strictness swap,
outward literal and equality flip) edit it as they edit awhen:, and each mutant is compiled,
synthesized and scored like thewhen:ones. Its id names the key after the outcome, such as
guard-negate/<command>/<outcome>/when_subjector
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). Awhen: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 ofwhen_subject:and a row set'swhereandforall
are not mutated. -
ess verify conform mutatesays why of everysets:entry that has nosets-dropmutant.
Wheresets-dropis selected,--targetand--emitprint, after their own output and on
stderr in every format, onenote: sets-drop/<command>/<outcome>/<field> has no mutant (<reason>): …per such entry, the reason one ofcreates_row,source_not_inputand
optional_field. A model whose every write is on a creating branch got nosets-dropmutant 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 interpretedruns authoredess-scenario/1
scenarios in the audit.DIRis read and refused asess verify conform run --scenariosreads
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 clockrun --target interpreteduses, so anowguard is decided at the step's instant and a scenariorunpasses
executes here too. One the baseline fails refuses the audit withESS-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 killedinconclusive(orunwitnessed),
neversurvived. Aprecedence-swapwhose branches answer alike staysequivalent: 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 becausemutateran no authored scenario (#515).
The scenario-clock runner is public asess_conformance::now_offset::scenario_clock_runner.
--scenariosis refused with--target billingand--target oracle-fixture, which implement no
mutant, and with--emit,--collectand--known-failing. Without--scenariosevery report
and manifest keeps its bytes; no persisted field is added and no format moves. The library entry
point isess_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:withinstance:from input), or refuse it through
when_subject:, declares its answer for an identity no row carries, where validation refused the
marker withunreachable_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 creatingunknown_instance:beside apreserves:branch alone is still
refusedconflicting_declaration, and a preserving command without the marker synthesizes the
scenarios it did (#535,
docs/design/typed-literals-and-unknown-instances.mdsection 2b). - A literal written through a newtype is refused as
type_mismatchwhere it lies outside that
newtype'salphabet:or makes one of itsinvariants:other than true, insets:,payload:,
a binding'smapping:, an enum variant'sattributes:and a view's default row alike, with the
sentence an input'sexample:gets for the same value. Before, only the representation was
checked, socode: xyzover a newtype drawn fromab, orlevel: 0over 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 backunsupportedinstead of testing the
non-disclosure rule. A model none of whose commands reads its caller keeps its suite bytes
(#550). -
The generated explorers (Go
Exploreand the TypeScriptexplore) 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 adeletes:outcome stays excluded as before
(#536). -
The generated explorers' model applies a selected-rows effect (
moves:orupdates:with
instances: {where: …}) to every row the filter selects, as synthesis and the interpreter do:
amoves:takes each selected row resting in the transition'sfromstates and skips the
others, and the outcome'ssets: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 amoves:'sfromstates is
skipped before its filter is read, and anOptionalfield 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 inundeterminedand the draw redrawn. A bulk
deletes:withinstances:is now excluded by name, as a singledeletes:is, and so is an
outcome with anaffects:entry, which the model does not apply: such a command was drawn
before and a correct target failed with a view disagreement on the rowsaffects:wrote. The
sequential explorer also draws aDecimalinput as it
draws anIntegerone, instead of excluding the command ("is adecimal")
(#538). -
The generated sequential explorers stop sending a command the target answers
ErrUnsupported
(TypeScript: throwsunsupported) 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 inexcludedwith the reason it had before, so
allowExcludedand the coverage check read as they did. A command driven elsewhere is reported in
a new, additive result fieldrefused(GoRefused []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 inexcludedOutcomes. The strict assertion
(assertExploredwithoutallowExcluded) fails on a nonemptyrefused, as it failed on the
exclusion each refusal made before, andallowExcludedaccepts 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/1document carryingconformanceswith anullvalue (conformances:,
conformances: ~, JSONnull) is refused asunsupported_format, asconformances: []is,
instead of reading as a document without the key and compiling./2and later read anull
conformancesas the absent key, as before (#525). -
No ESS crate depends on
serde_yamlorunsafe-libyamlany more, socargo deny check advisoriesin 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 (theinfra-*crates throughess_primitives::yaml). Its backend is
serde_norway0.9.42, a stopgap: a maintained fork ofserde_yaml0.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 goemits a module that builds when bounded contexts reference
each other in a loop, for example a command outcome inpublishrefusing with an error declared
insession,sessionreferencingsubscription, andsubscriptionreferencing a type of
publish. Each bounded context had its own package undertypes/, so those packages imported
each other andgo buildfailed withimport 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 oneUnimplementedstub 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 mutateno longer reports aprecedence-swapequivalent 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--scenariosan authored scenario that saw the escalation killed
nothing. Such a swap now carries noidentical_answerand 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.