diff --git a/evals/create-full-stack-app/cases.json b/evals/create-full-stack-app/cases.json index a0b40e3..f780dae 100644 --- a/evals/create-full-stack-app/cases.json +++ b/evals/create-full-stack-app/cases.json @@ -79,14 +79,15 @@ { "id": "add-ordinal-enum-with-minted-ids", "should_trigger": true, - "prompt": "The installed firstdraft CLI includes plan subject-id. Add a required movie.priority Field with ranked Low, Medium, and High choices to the staged Plan for local review. Do not push it.", + "prompt": "The installed firstdraft CLI includes plan subject-id. Add a required movie.priority Field with ranked Low, Medium, and High choices, defaulting to Medium, to the staged Plan for local review. Do not push it.", "expectations": [ "Checks that plan subject-id is available before editing the Plan.", "Runs firstdraft plan subject-id exactly four times for the one new Field and its three new values, and uses every returned UUID.", "Never fabricates a UUIDv7 or copies an example or fixture UUID.", "Authors an enum Field with settings.values in low, medium, high order and settings.ordinal set to true.", + "Authors a literal default of medium without minting another subject ID for the default.", "Preserves every existing subject_uuid and leaves .firstdraft/state.json unopened and unchanged.", - "Does not invent an unrequested default or Validation.", + "Does not invent a Validation.", "Does not run plan init or plan push." ], "artifacts": [ @@ -107,8 +108,8 @@ "should_trigger": true, "prompt": "Send the staged Foundation Plan to First Draft for diagnostics. Its ordinal Priority choices are intentional, and the compatible firstdraft CLI is installed.", "expectations": [ - "Recognizes the enum Field, its ordered values, and ordinality as supported by the reviewed bounded importer.", - "Preserves the enum Field and every existing subject_uuid without weakening the modeled product meaning.", + "Recognizes the enum Field, its ordered values, ordinality, and literal medium default as supported by the reviewed bounded importer.", + "Preserves the enum Field, its default, and every existing subject_uuid without weakening the modeled product meaning.", "Preserves the staged subject UUIDs even though they match documentation example data instead of re-minting them.", "Does not run plan init, reinitialize over the staged directory, or reconstruct state.", "Leaves .firstdraft/state.json unopened and lets the CLI update it only after verified success.", @@ -128,12 +129,39 @@ } ] }, + { + "id": "rename-defaulted-enum-value", + "should_trigger": true, + "prompt": "In the staged Plan, rename the task.priority choice medium/Medium to standard/Standard and update everything else in the Plan that refers to it. Keep this local; do not push it.", + "expectations": [ + "Preserves the renamed value's existing subject_uuid and every other subject identity.", + "Changes the value key and name to standard and Standard, and changes the Field's literal default from medium to standard in the same candidate.", + "Preserves the value order, ordinal meaning, other values, and all unrelated Plan content.", + "Does not run plan subject-id because the rename creates no new subject.", + "Leaves .firstdraft/state.json unopened and unchanged.", + "Does not run plan init or plan push or make a network request." + ], + "artifacts": [ + { + "path": "evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json", + "role": "input", + "stage_as": ".firstdraft/foundation-plan.json" + }, + { + "path": "evals/create-full-stack-app/fixtures/state-placeholder.txt", + "role": "input", + "stage_as": ".firstdraft/state.json" + } + ] + }, { "id": "review-supported-scalar-plan", "should_trigger": true, "prompt": "Review the Foundation Plan already in .firstdraft against the current First Draft import boundary. Keep this local; do not push it.", "expectations": [ "Recognizes all ten scalar Field types as supported by the reviewed bounded importer.", + "Recognizes the published_at current_time environment default as structurally supported and preserves it.", + "Recognizes the rating literal 7.5 as a canonical decimal string and does not rewrite it as a JSON number.", "Preserves every Field, subject_uuid, and the Field Primary Descriptor.", "Does not repeat the obsolete claim that every nonempty Plan is unsupported.", "Does not run plan push or make another network request.", @@ -155,11 +183,12 @@ { "id": "author-without-local-validator", "should_trigger": true, - "prompt": "Use First Draft to create and send a Foundation Plan for Scalar Showcase, application key scalar_showcase. Model one Movie Entity with one Field of each currently importable scalar type, using a required short_text title as its Primary Descriptor. The compatible firstdraft CLI is installed, but no JSON Schema 2020-12 validator is available. Do not install or implement one.", + "prompt": "Use First Draft to create and send a Foundation Plan for Scalar Showcase, application key scalar_showcase. Model one Movie Entity with one Field of each currently importable scalar type, using a required short_text title as its Primary Descriptor and a decimal rating defaulting to 7.5. The compatible firstdraft CLI is installed, but no JSON Schema 2020-12 validator is available. Do not install or implement one.", "expectations": [ "Uses Foundation Plan 0.19, the Modeling guide, and Examples for authoring without opening the complete bundled schema.", "Runs plan init with the provided application key and name, then runs plan subject-id exactly eleven times for one Entity and ten Fields.", "Authors one Movie Entity with exactly one Field of each supported scalar type and a required movie.title Field Primary Descriptor.", + "Authors movie.rating as a decimal with literal default value \"7.5\" as a canonical string, never the JSON number 7.5.", "Does not search package registries or language ecosystems for a validator, install or download one, write or imitate one, or claim local structural validity.", "Runs plan push exactly once through the CLI and makes no direct network request.", "Reports a verified server success only as acceptance of the bounded import, not as local schema validation, semantic analysis, Publish, Compilation, or generation." @@ -224,8 +253,8 @@ "should_trigger": true, "prompt": "First Draft returned the attached capability diagnostics for my staged Plan. Fix it and keep pushing until it passes.", "expectations": [ - "Recognizes both unsupported_capability pointers as current default and Validation server gaps rather than invalid product meaning.", - "Preserves the now-supported enum and does not delete the default or Validation merely to force a successful response.", + "Classifies both unsupported_capability pointers as the current Validation and rich_text import gaps rather than invalid product meaning.", + "Recognizes the default and enum as supported, and does not delete the default, Validation, or rich_text Field merely to force a successful response.", "Preserves the complete local Plan and every existing subject_uuid.", "Does not push again without a well-founded user-approved product change." ], diff --git a/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json index 794c3cd..d4923ae 100644 --- a/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json @@ -31,6 +31,10 @@ "name": "Priority", "type": "enum", "required": true, + "default": { + "kind": "literal", + "value": "medium" + }, "settings": { "values": [ { diff --git a/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json b/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json index d445306..120ce1c 100644 --- a/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/supported-scalars.foundation-plan.json @@ -37,14 +37,22 @@ "key": "published_at", "name": "Published at", "type": "datetime", - "required": true + "required": true, + "default": { + "kind": "environment", + "name": "current_time" + } }, { "subject_uuid": "01900000-0000-7000-8000-000000000204", "key": "rating", "name": "Rating", "type": "decimal", - "required": true + "required": true, + "default": { + "kind": "literal", + "value": "7.5" + } }, { "subject_uuid": "01900000-0000-7000-8000-000000000205", diff --git a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json index 011e4c0..b5441e2 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json +++ b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json @@ -1,12 +1,12 @@ { - "source_sha256": "eb44957b1676b7b118e4081691f5dd3c684dc14fd498f8eb9539319c180b614b", + "source_sha256": "63a58234608f3b2698176b80f315b3057358c88043ded8838d3ae95de5a5dd35", "diagnostics": [ { "code": "foundation_plan.import.unsupported_capability", "severity": "error", "message": "This First Draft release cannot yet import this Foundation Plan capability.", "location": { - "source_pointer": "/application/entities/0/fields/0/default" + "source_pointer": "/application/entities/0/fields/0/validations" }, "subject": null, "related_locations": [], @@ -17,7 +17,7 @@ "severity": "error", "message": "This First Draft release cannot yet import this Foundation Plan capability.", "location": { - "source_pointer": "/application/entities/0/fields/0/validations" + "source_pointer": "/application/entities/0/fields/2/type" }, "subject": null, "related_locations": [], diff --git a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json index 68bb6d9..c294f61 100644 --- a/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json +++ b/evals/create-full-stack-app/fixtures/unsupported-field-capabilities.foundation-plan.json @@ -52,6 +52,13 @@ } ] } + }, + { + "subject_uuid": "01900000-0000-7000-8000-000000000306", + "key": "description", + "name": "Description", + "type": "rich_text", + "required": false } ] } diff --git a/skills/create-full-stack-app/SKILL.md b/skills/create-full-stack-app/SKILL.md index 96c5a8e..4fe060d 100644 --- a/skills/create-full-stack-app/SKILL.md +++ b/skills/create-full-stack-app/SKILL.md @@ -12,8 +12,8 @@ concurrency, and network behavior in the `firstdraft` CLI. This Skill is experimental. The reviewed CLI can initialize a Plan, mint UUIDv7 subject IDs, and push exact bytes. The reviewed server can create and replace empty drafts plus a bounded subset of Entities, ten scalar Field kinds, -enum Fields with ordered values, and Field or system-Field Primary Descriptors. These slices are not released end -to end. +enum Fields with ordered values, schema-valid tagged Field defaults, and Field or system-Field Primary Descriptors. +These slices are not released end to end. ## Load the relevant references @@ -75,6 +75,7 @@ preserve its existing subject UUIDs. path in the same candidate. Give a replacement concept a new UUID. 5. Use `firstdraft plan subject-id` for each genuinely new subject. If the capability check failed, do not invent a UUIDv7 or copy an example UUID; explain that this CLI cannot yet add subjects safely. + A Field default is a value owned by its Field, not a subject; never mint an ID for it. 6. Omit unsupported prose, secrets, arbitrary code, ordinary empty optional collections, and structural `null` placeholders. 7. Ask the user about materially ambiguous product meaning. Do not silently choose destructive relationship diff --git a/skills/create-full-stack-app/references/diagnostics-and-recovery.md b/skills/create-full-stack-app/references/diagnostics-and-recovery.md index 6e76700..ddd7919 100644 --- a/skills/create-full-stack-app/references/diagnostics-and-recovery.md +++ b/skills/create-full-stack-app/references/diagnostics-and-recovery.md @@ -26,7 +26,7 @@ A `422` response binds diagnostics to the submitted bytes with `source_sha256`: "severity": "error", "message": "This First Draft release cannot yet import this Foundation Plan capability.", "location": { - "source_pointer": "/application/entities/0/fields/0/default" + "source_pointer": "/application/entities/0/fields/0/validations" }, "subject": null, "related_locations": [], @@ -42,6 +42,15 @@ Diagnostic locations have one of two shapes: - `location.line` and `location.column` are positive one-based coordinates for source-level problems such as malformed JSON or duplicate object names. +`foundation_plan.json.number_out_of_range` and `foundation_plan.json.number_not_round_trippable` use the root +pointer `""` because the loader checks the whole document's PostgreSQL JSON storage boundary before subject-level +analysis. Either can concern a numeric literal nested in a Field default. Scan the raw source for authored +JSON-number literals; parsing and reserializing can erase exponent or negative-zero spelling. If more than one +could explain the root diagnostic, identify the candidates for the user and do not guess which one to change. +Preserve the intended representation rather than rounding or coercing a value merely to pass. A `decimal` literal +is already authored as a canonical decimal string, not a JSON number; encode that documented semantic form when +the user's intent is unambiguous. + `subject` optionally identifies the typed readable subject the diagnostic concerns. `related_locations` lists additional pointer or coordinate locations needed to understand the same problem. `suggestions` contains optional candidate values or repairs; it does not authorize a change. diff --git a/skills/create-full-stack-app/references/examples.md b/skills/create-full-stack-app/references/examples.md index 502f48e..601e277 100644 --- a/skills/create-full-stack-app/references/examples.md +++ b/skills/create-full-stack-app/references/examples.md @@ -110,6 +110,10 @@ the reviewed bounded importer. Here, priority order carries semantic rank, so `o "name": "Priority", "type": "enum", "required": true, + "default": { + "kind": "literal", + "value": "medium" + }, "settings": { "values": [ { @@ -138,8 +142,10 @@ the reviewed bounded importer. Here, priority order carries semantic rank, so `o } ``` -The Entity, Fields, and every enum value have independent UUIDs. Preserve a value's UUID when renaming or -reordering it. Omit `ordinal` when order is presentational rather than ranked. +The Entity, Fields, and every enum value have independent UUIDs. The default is a tagged value owned by the Field, +so it has no UUID and does not require `plan subject-id`. Its literal names the selected enum value by owner-local +key. If `medium` is renamed, update the default in the same candidate while preserving that value's UUID. Omit +`ordinal` when order is presentational rather than ranked. ## Stored and reverse relationship diff --git a/skills/create-full-stack-app/references/foundation-plan-019.md b/skills/create-full-stack-app/references/foundation-plan-019.md index f2d936e..1fdadb7 100644 --- a/skills/create-full-stack-app/references/foundation-plan-019.md +++ b/skills/create-full-stack-app/references/foundation-plan-019.md @@ -20,7 +20,7 @@ authorized. - Structural validity does not prove readable-link resolution, whole-application consistency, target support, or compilability. - The reviewed conditional PUT imports empty drafts and a bounded subset of Entities, ten scalar Field kinds, enum - Fields with ordered values, and Field or system-Field Primary Descriptors. + Fields with ordered values, schema-valid tagged Field defaults, and Field or system-Field Primary Descriptors. - There is no released end-to-end CLI/API workflow, complete nonempty import, GET or pull operation, complete semantic analyzer, Publish action, Compilation action, or generated Foundation. @@ -28,7 +28,7 @@ The bundled schema was copied from the [First Draft source at revision `12fa2a6`](https://github.com/firstdraft/firstdraft/blob/12fa2a6bcac122196d55f5528fbc3f1363c684e3/docs/architecture/design/foundation-plan.schema.json) and has SHA-256 `5994c41f65eab52f92020fa24437e76b6957b7016ccf231dce06e8097f0b34b5`. The reviewed public API baseline is -[`3282954b6eefef4ab47ccba1c2ee7008315bee92`](https://github.com/firstdraft/firstdraft/commit/3282954b6eefef4ab47ccba1c2ee7008315bee92) +[`944673772ef7c5bf40ff67e65d8266556ab75f08`](https://github.com/firstdraft/firstdraft/commit/944673772ef7c5bf40ff67e65d8266556ab75f08) and contains those same schema bytes. The reviewed public CLI baseline is [`0681afd48d7825a7a1a0112e248f3013d0123743`](https://github.com/firstdraft/cli/commit/0681afd48d7825a7a1a0112e248f3013d0123743); @@ -73,9 +73,10 @@ Ordinary replacement must retain the Project's target and target-profile pin. UUIDs. Enum values, state-machine states and transitions, and data records are examples of identity-bearing nested -subjects. Link-keyed assignments, ordered terms, settings, and singleton configuration inherit identity from -their owner. Search the schema for the subject's exact `$defs` name and use diagnostics rather than guessing -whether an unfamiliar object needs an ID. +subjects. Defaults, link-keyed assignments, ordered terms, settings, and singleton configuration inherit identity +from their owner. A Field default has no `subject_uuid`; adding, changing, or clearing one preserves the Field's +identity. Search the schema for the subject's exact `$defs` name and use diagnostics rather than guessing whether +an unfamiliar object needs an ID. ## Ownership @@ -100,7 +101,10 @@ App Schema artifact. `"ios": {}` enables that feature; omission declines it. - `settings.within: []` deliberately means one global position scope. - Use `null` only where the schema gives it a semantic meaning, not as structural filler. -- Omission and an explicit scalar default mean the same thing, but examples normally omit default-valued settings. +- For an optional scalar setting with a declared default, omission and that explicit value mean the same thing; + examples normally omit default-valued settings. +- Omitting a Field's `default` means it has no authored default. `{"kind":"literal","value":null}` is instead an + authored literal-null default. ## Current conditional PUT boundary @@ -136,17 +140,38 @@ descriptors are not yet supported. A Field may use these types: - `url` For every supported type, the importer retains schema-valid combinations of `subject_uuid`, `key`, `name`, `type`, -`required`, `notes`, `immutable`, `comparison`, `normalizations`, `encrypted_at_rest`, and `redact_from_logs`. +`required`, `default`, `notes`, `immutable`, `comparison`, `normalizations`, `encrypted_at_rest`, and +`redact_from_logs`. An `enum` Field additionally requires `settings.values`, a nonempty array in stable order. Each value has its own `subject_uuid`, owner-local `key`, and human-facing `name`; mint an ID for each new value with `firstdraft plan subject-id`. Set the optional `settings.ordinal` to `true` only when the order carries semantic rank. Omit it when the order is presentational because omission and `false` are equivalent. Preserve a value's -UUID through renames, reordering, and coherent moves between enum Fields. +UUID through renames, reordering, and coherent moves between enum Fields. An enum literal default contains the +selected value's owner-local `key`, not its UUID. Update that literal in the same candidate when renaming the value, +while preserving the value's UUID. + +A Field `default` is one closed tagged Value. Its tag is `literal`, `environment`, `environment_path`, or +`reference_record`. A literal wraps its JSON value under `value`; an environment names `current_account`, +`current_date`, or `current_time`. A `decimal` literal uses a canonical, non-exponent decimal string: `"0"`, +`"-0.5"`, `"12"`, and `"12.34"` are valid, while a JSON number, plus sign, negative zero, exponent, a redundant +leading zero before another integer digit, or trailing fractional zero is not. The two link-bearing variants use +readable locators. Inspect only the matching `$defs` definition when authoring one of those variants. Their +Account, Association, or reference-data dependencies may keep the complete candidate outside the current import +subset; preserve valid product meaning and report the capability gap rather than replacing a linked default with a +weaker literal. + +The bounded importer structurally retains all four schema-valid tags without checking their type or resolving +their links. It retains the tagged object's decoded JSON meaning, including integer-versus-floating-point +representation, while the exact submitted bytes remain in the Project Head. + +This retention is structural, not default analysis. It does not prove literal compatibility with the Field, +enum membership, readable-locator resolution, nullability, normalization behavior, or Compiler lowering. Preserve +the intended default when reporting any later semantic gap. Scalar Fields have no `settings` object, and enum `settings` admits only `values` and optional `ordinal`; any other settings shape is structurally invalid rather than an importer capability gap. Schema-valid Field types outside -the list above, Field defaults, Validations, derivations, References, Associations, and other Entity or Application -capabilities remain unsupported. One unsupported pointer rejects the complete conditional PUT with +the list above, Validations, derivations, References, Associations, and other Entity or Application capabilities +remain unsupported. One unsupported pointer rejects the complete conditional PUT with `foundation_plan.import.unsupported_capability` and no mutation. That diagnostic describes server capability, not invalid product meaning. Preserve the authored Plan and report the exact gap. diff --git a/test/repository.test.mjs b/test/repository.test.mjs index 31b7a77..842ec82 100644 --- a/test/repository.test.mjs +++ b/test/repository.test.mjs @@ -18,7 +18,7 @@ const foundationPlanTarget = { const foundationPlanSchemaDigest = "5994c41f65eab52f92020fa24437e76b6957b7016ccf231dce06e8097f0b34b5"; const foundationPlanServerBaseline = - "3282954b6eefef4ab47ccba1c2ee7008315bee92"; + "944673772ef7c5bf40ff67e65d8266556ab75f08"; const foundationPlanCliBaseline = "0681afd48d7825a7a1a0112e248f3013d0123743"; const supportedScalarFieldTypes = [ @@ -40,6 +40,7 @@ const supportedFieldProperties = [ "name", "type", "required", + "default", "notes", "immutable", "comparison", @@ -244,10 +245,55 @@ test("bounded importer prose remains bound to the exact allowlists", async () => documentedEnumSection[0], /Preserve a value's\s+UUID through renames, reordering, and coherent moves between enum Fields/, ); + assert.match( + documentedEnumSection[0], + /An enum literal default contains the\s+selected value's owner-local `key`, not its UUID\.[\s\S]*?Update that literal in the same candidate when renaming the value,\s+while preserving the value's UUID/, + ); assert.match( foundationPlanReference, /Scalar Fields have no `settings` object, and enum `settings` admits only `values` and optional `ordinal`; any other\s+settings shape is structurally invalid rather than an importer capability gap/, ); + assert.match( + foundationPlanReference, + /A Field `default` is one closed tagged Value\. Its tag is `literal`, `environment`, `environment_path`, or\s+`reference_record`/, + ); + assert.match( + foundationPlanReference, + /A `decimal` literal uses a canonical, non-exponent decimal string[\s\S]*?a JSON number, plus sign, negative zero, exponent, a redundant\s+leading zero before another integer digit, or trailing fractional zero is not/, + ); + assert.match( + foundationPlanReference, + /bounded importer structurally retains all four schema-valid tags without checking their type or resolving\s+their links/, + ); + assert.match( + foundationPlanReference, + /A Field default has no `subject_uuid`; adding, changing, or clearing one preserves the Field's\s+identity/, + ); + assert.match( + foundationPlanReference, + /Omitting a Field's `default` means it has no authored default[\s\S]*?authored literal-null default/, + ); + assert.match( + foundationPlanReference, + /retention is structural, not default analysis[\s\S]*?does not prove literal compatibility[\s\S]*?Compiler lowering/, + ); + + const diagnosticsReference = await readFile( + path.join(referencesDirectory, "diagnostics-and-recovery.md"), + "utf8", + ); + assert.match( + diagnosticsReference, + /`foundation_plan\.json\.number_out_of_range` and `foundation_plan\.json\.number_not_round_trippable` use the root\s+pointer `""`/, + ); + assert.match( + diagnosticsReference, + /Scan the raw source for authored\s+JSON-number literals[\s\S]*?identify the candidates for the user and do not guess/, + ); + assert.match( + diagnosticsReference, + /A\s+`decimal` literal\s+is already authored as a canonical decimal string, not a JSON number/, + ); const examples = await readFile( path.join(referencesDirectory, "examples.md"), @@ -279,13 +325,17 @@ test("bounded importer prose remains bound to the exact allowlists", async () => ["low", "medium", "high"], ); assert.equal(ordinalField.settings.ordinal, true); - assert(!("default" in ordinalField)); + assert.deepEqual(ordinalField.default, { + kind: "literal", + value: "medium", + }); assert(!("validations" in ordinalField)); const identities = [ ordinalEntity.subject_uuid, ...ordinalEntity.fields.map(({ subject_uuid }) => subject_uuid), ...ordinalField.settings.values.map(({ subject_uuid }) => subject_uuid), ]; + assert.equal(identities.length, 6); assert.equal(new Set(identities).size, identities.length); const enumFixture = JSON.parse( @@ -358,6 +408,12 @@ test("validator routing preserves validation boundaries", async () => { assert.match(withoutValidator.prompt, /Do not install or implement one/); assertExpectation(withoutValidator, "without opening the complete bundled schema"); assertExpectation(withoutValidator, "plan subject-id exactly eleven times"); + assertExpectation( + withoutValidator, + "movie.rating", + "literal default value \"7.5\" as a canonical string", + "never the JSON number 7.5", + ); assertExpectation(withoutValidator, "plan push exactly once"); assertExpectation(withoutValidator, "claim local structural validity"); assertExpectation(withoutValidator, "acceptance of the bounded import"); @@ -489,6 +545,47 @@ test("revision evals stage existing Plan identity and private state", async () = stagedPlanArtifacts, ); } + const enumRenameEvaluation = cases.find( + ({ id }) => id === "rename-defaulted-enum-value", + ); + assert.deepEqual(enumRenameEvaluation.artifacts, [ + { + path: "evals/create-full-stack-app/fixtures/supported-enum.foundation-plan.json", + role: "input", + stage_as: ".firstdraft/foundation-plan.json", + }, + stagedPlanArtifacts[1], + ]); + assert( + enumRenameEvaluation.expectations.some((expectation) => + expectation.includes("existing subject_uuid"), + ), + "enum rename eval must preserve value identity", + ); + assert( + enumRenameEvaluation.expectations.some((expectation) => + expectation.includes("literal default from medium to standard"), + ), + "enum rename eval must update the dependent default", + ); + assert( + enumRenameEvaluation.expectations.some((expectation) => + expectation.includes("Does not run plan subject-id"), + ), + "enum rename eval must not mint a replacement identity", + ); + assert( + enumRenameEvaluation.expectations.some((expectation) => + expectation.includes("state.json unopened and unchanged"), + ), + "enum rename eval must preserve private CLI state", + ); + assert( + enumRenameEvaluation.expectations.some((expectation) => + expectation.includes("Does not run plan init or plan push or make a network request"), + ), + "enum rename eval must remain local", + ); const mintingEvaluation = cases.find( ({ id }) => id === "add-field-with-minted-id", ); @@ -523,6 +620,12 @@ test("revision evals stage existing Plan identity and private state", async () = ), "enum eval must bind value order", ); + assert( + enumEvaluation.expectations.some((expectation) => + expectation.includes("literal default of medium without minting another subject ID"), + ), + "enum eval must reuse the value key without minting a default ID", + ); assert( enumEvaluation.expectations.some((expectation) => expectation.includes("Does not run plan init or plan push"), @@ -621,6 +724,32 @@ test("bounded import evals bind supported and unsupported Plan state", async () assert(descriptorField, "supported fixture: descriptor Field does not resolve"); assert.equal(descriptorField.type, "short_text"); assert.equal(descriptorField.required, true); + const publishedAt = supportedEntity.fields.find( + ({ key }) => key === "published_at", + ); + assert(publishedAt, "supported scalar fixture: missing published_at Field"); + assert.deepEqual(publishedAt.default, { + kind: "environment", + name: "current_time", + }); + assert( + supportedEvaluation.expectations.some((expectation) => + expectation.includes("current_time environment default"), + ), + "supported scalar eval must recognize the environment default", + ); + const rating = supportedEntity.fields.find(({ key }) => key === "rating"); + assert(rating, "supported scalar fixture: missing rating Field"); + assert.deepEqual(rating.default, { + kind: "literal", + value: "7.5", + }); + assert( + supportedEvaluation.expectations.some((expectation) => + expectation.includes("rating literal 7.5 as a canonical decimal string"), + ), + "supported scalar eval must preserve the decimal string default", + ); assert.equal( new Set([ supportedEntity.subject_uuid, @@ -681,9 +810,13 @@ test("bounded import evals bind supported and unsupported Plan state", async () ["low", "medium", "high"], ); assert.equal(supportedEnumField.settings.ordinal, true); + assert.deepEqual(supportedEnumField.default, { + kind: "literal", + value: "medium", + }); assert( supportedEnumEvaluation.expectations.some((expectation) => - expectation.includes("supported by the reviewed bounded importer"), + expectation.includes("literal medium default as supported"), ), "supported enum eval must recognize the import boundary", ); @@ -715,6 +848,24 @@ test("bounded import evals bind supported and unsupported Plan state", async () const unsupportedEvaluation = cases.find( ({ id }) => id === "unsupported-field-capabilities", ); + assert( + unsupportedEvaluation.expectations.some((expectation) => + expectation.includes("both unsupported_capability pointers"), + ), + "unsupported eval must classify every remaining import gap", + ); + assert( + unsupportedEvaluation.expectations.some((expectation) => + expectation.includes("default and enum as supported"), + ), + "unsupported eval must preserve the admitted default and enum", + ); + assert( + unsupportedEvaluation.expectations.some((expectation) => + expectation.includes("Validation, or rich_text Field"), + ), + "unsupported eval must preserve both unsupported capabilities", + ); assert.deepEqual(unsupportedEvaluation.artifacts, [ { path: "evals/create-full-stack-app/fixtures/unsupported-field-capabilities-diagnostics.json", @@ -740,6 +891,11 @@ test("bounded import evals bind supported and unsupported Plan state", async () unsupportedFields[1].settings.values.map(({ key }) => key), ["draft"], ); + assert.equal(unsupportedFields[2].type, "rich_text"); + assert.equal( + unsupportedFields[2].subject_uuid, + "01900000-0000-7000-8000-000000000306", + ); const response = JSON.parse( await readFile( path.join( @@ -762,11 +918,11 @@ test("bounded import evals bind supported and unsupported Plan state", async () [ [ "foundation_plan.import.unsupported_capability", - "/application/entities/0/fields/0/default", + "/application/entities/0/fields/0/validations", ], [ "foundation_plan.import.unsupported_capability", - "/application/entities/0/fields/0/validations", + "/application/entities/0/fields/2/type", ], ], );