Generated values
Your shop needs a customer identity before it can record a registration, an order identity when checkout starts, and an opaque invitation code. Asking callers to invent these values turns server decisions into request inputs. Screenplay separates them: a concept declares how values are generated, and a command property declares that the server generates it. For the full cross-product journey, follow the shop tutorial work; this page explains the model contract.
The concept owns the strategy
Section titled “The concept owns the strategy”concept CustomerId : String generate ulid prefix "cus_" case lower validate matches "^cus_[0-7][0-9a-hjkmnp-tv-z]{25}$"concept InviteCode : String generate nanoid validate matches "^[A-Za-z0-9_-]{21}$"module Shop feature Customers slice StateChange Register command Register customerId CustomerId generated identifier inviteCode InviteCode generated produces event CustomerRegistered inviteCode InviteCode = inviteCode returns customerIdGeneration follows authorization and validation. Generated values are not request inputs, cannot authorize the caller, and cannot appear in pre-generation rules. A strategy does not automatically add validation to input or stored values; changing it does not retroactively invalidate old identities.
A Uuid concept has implicit uuid generation. generate uuid without modifiers on that concept has the same executable bytes and revision as omitting the line. String concepts need an explicit strategy. Required scalar properties only are admitted; optional values and collections are not.
Freshness is about the whole source
Section titled “Freshness is about the whole source”A model with at least one serialized concept generation member selects ESM v15 or a later version when another feature needs it. Its generated event-source destinations are Guarded: an append to a value generated in the invocation requires that the whole source has no existing facts, regardless of stream or event type. Several initial facts in one command are fine. A skipped production does not make a freshness claim. A violation rejects the whole command with Concurrency and appends nothing; generation is not silently retried.
Models without a serialized generation strategy remain Unguarded, including a model whose only declaration is unmodified Uuid generate uuid, even if rules select V20. They retain legacy fixture reuse. A model declaring a serialized strategy guards its implicit UUID generated values too. Providers must carry this policy explicitly when realizing commands; freshness is not idempotency and does not authorize access.
Pin a value, not a random seed
Section titled “Pin a value, not a random seed”The reference evaluator never draws randomness. Specifications pin a generated identifier with for, and other generated properties with generated <name> = <value>:
concept OrderId : String generate typeid "order"module Shop feature Orders slice StateChange StartOrder command StartOrder id OrderId generated identifier produces event OrderStarted returns id specification StartsWithThePinnedIdentity when StartOrder for "order_0000000000e008000000000000" then OrderStarted for "order_0000000000e008000000000000" then returns "order_0000000000e008000000000000"A model declaring a serialized strategy checks each generated fixture’s exact encoding, size, letter case, UUID version and variant. A generated concept with rules selects ESM V20 and checks its fixtures against both shape and rules, including implicit UUIDv4 generation. A failing rule returns Rejected(Contract) with Generated fixture '<property>' fails concept '<Concept>' rules; it is a server contract defect, not a client validation error, and nothing is redrawn. Opaque rules route to Unsupported because the reference evaluator never runs attached code. Older rule-free, implicit-UUID models keep canonical UUID type validation. Seeds are not portable: a provider or algorithm upgrade could change their output.
TypeID carries time, not a tenant slot. Keep tenant isolation in the namespace; a tenant-bearing value needs a separate concept such as TenantOrderId, not a modifier on OrderId.
Make the strategy and rules agree
Section titled “Make the strategy and rules agree”The customer and invitation rules above are Proven: every value those exact shapes produce satisfies them. A Refuted rule stops binding with PLAY0688, naming the strategy and a quoted violating value. An Unproven rule produces PLAY0689 with the reason: a regex outside fragment R, a construction budget, or opaque validation. That warning is not a proof. Providers still validate every generated value and treat a failure as a server defect; they never silently draw another one.
Rules select V20 even when an implicit-UUID concept serializes no strategy. No proof status is serialized. Changing generation never adds or changes validation automatically, so stored values keep their authored rules. The strategy reference describes fragment R and proof limits.
Work with strategies through MCP
Section titled “Work with strategies through MCP”You can inspect and change the concept’s strategy without editing generated values on each command. declaration-details with kind: "Concept" and view: "summary" shows the authored strategy and modifiers, exact random bits, the count at 1% collision probability, and compiler refusal codes. An implicit UUID default has no authored generation block.
The generation.executable object reports status: "admitted" only after the whole model produces an executable model, "refused" for compiler errors at this concept, its generation or its rules, or "notAssessed" with a reason when unrelated failures prevent an executable model. Its diagnosticCodes lists errors at this declaration, so an empty list is not proof of admission. Invalid alphabets or non-positive sizes/counts report null entropy and collision estimates with entropyUnknownReason; inspection preserves the compiler diagnostics rather than failing.
The existing propose-ast tool replaces a ConceptSyntax with its typed GenerationSyntax; propose-source accepts the equivalent whole source document. Set, change or remove generation, review the proposal, then explicitly apply it. Canonical printing retains generate. Authoring acceptance does not imply executable readiness: preview strategies still carry the compiler’s refusals; generated-concept rules report their proof diagnostics.
For PLAY0683 or PLAY0684, the repair inventory can offer a larger NanoID or byte count reaching 64 random bits. A non-identifier mapped into one event property can instead gain an explicit unique constraint (PLAY0683). These are alternative model changes, not silent fixes: use the selected subject with propose-repair, review the changed documents, then apply. A length change affects every property using the concept; a unique constraint changes append-time behavior.
For PLAY0688, propose-repair can replace the refuted rule with matches "<derived pattern>". Review it carefully: the rule also validates input and stored values of that concept, not just new fixtures. Checksum repairs remain unadmitted. See the MCP generation reference for the fields and repair contract.
High-entropy built-ins are executable: uuid, uuidv7, ulid, typeid, nanoid, and bytes. pattern, random, and rule are parsed and printed but not yet executable. sequence is reserved. Compliance markers on explicitly generated concepts remain refused. In the shop, ApiKey is a secret value (32 random bytes), never an event-source identity: use a separate ApiKeyId. Its secret/encryption contract protects storage; [NotAudited] only withholds command values from the causation chain, not event payloads or logs. Generated secrets await separate admission. See strategy reference and choosing a strategy.