Status: Draft · Companion to: Formspec v1.0 · Date: 2025-07
A Changelog Document is a JSON document that enumerates the differences between two versions of a Formspec Definition. It supports:
Terminology and Definition structure (items, binds, shapes, etc.) follow Formspec v1.0. Semver semantics follow §6.2; migration objects follow §6.7.
$formspecChangelog,
definitionUrl, fromVersion,
toVersion, semverImpact, and
changes.schemas/changelog.schema.json;
generated references are the structural contract. A Changelog Document is a JSON object at the top level.
| Pointer | Field | Type | Required | Notes | Description |
|---|---|---|---|---|---|
#/properties/$formspecChangelog |
$formspecChangelog |
string |
yes | const: “1.0”; critical |
Changelog specification version. MUST be ‘1.0’. |
#/properties/$schema |
$schema |
string |
no | — | — |
#/properties/changes |
changes |
array |
yes | critical | Ordered array of Change objects. Each entry describes one atomic modification to a definition element. |
#/properties/definitionUrl |
definitionUrl |
string |
yes | critical | Canonical URL of the Definition whose versions are compared. Must match the definition’s top-level ‘url’ property. |
#/properties/fromVersion |
fromVersion |
string |
yes | critical | Base version (before changes). Interpreted per the definition’s versionAlgorithm (default: semver). |
#/properties/generatedAt |
generatedAt |
string |
no | — | ISO 8601 timestamp when this changelog was generated. |
#/properties/semverImpact |
semverImpact |
string |
yes | enum: “major”, “minor”,
“patch”; critical |
Maximum impact across all changes. Must equal the highest impact in the changes array: breaking → major, compatible → minor, cosmetic → patch. |
#/properties/summary |
summary |
string |
no | — | Human-readable summary of changes for release notes. |
#/properties/toVersion |
toVersion |
string |
yes | critical | Target version (after changes). Interpreted per the definition’s versionAlgorithm (default: semver). |
The generated table above is the canonical structural contract for Changelog Document top-level properties.
semverImpact MUST equal the maximum impact across all
entries in changes (breaking → major, compatible → minor,
cosmetic → patch).
{
"$schema": "https://formspec.org/changelog/v1/schema.json",
"definitionUrl": "https://example.org/forms/grant-application",
"fromVersion": "2.1.0",
"toVersion": "3.0.0",
"generatedAt": "2025-07-10T14:30:00Z",
"semverImpact": "major",
"summary": "Removes legacy budget fields; adds new personnel section.",
"changes": [
{
"type": "removed",
"target": "item",
"path": "items.budget.legacyCost",
"key": "legacyCost",
"impact": "breaking",
"description": "Removed deprecated cost field.",
"before": { "key": "legacyCost", "dataType": "currency" },
"migrationHint": "drop"
},
{
"type": "added",
"target": "item",
"path": "items.budget.personnel",
"key": "personnel",
"impact": "compatible",
"description": "New personnel budget group.",
"after": { "key": "personnel", "itemType": "group" }
}
]
}Each Change describes a single atomic modification to a Definition element.
| Property | Type | Req | Description |
|---|---|---|---|
type |
enum | REQUIRED | "added", "removed",
"modified", "moved",
"renamed" |
target |
enum | REQUIRED | "item", "bind", "shape",
"optionSet", "dataSource",
"screener", "migration",
"metadata" |
path |
string | REQUIRED | Dot-path to the affected element (e.g.,
"items.budget.personnel"). |
key |
string | OPTIONAL | The item key when target is
"item". |
impact |
enum | REQUIRED | "breaking", "compatible",
"cosmetic" |
description |
string | RECOMMENDED | Human-readable description of the change. |
before |
any | OPTIONAL | Previous value or structural fragment. Present for
modified, removed, renamed,
moved. |
after |
any | OPTIONAL | New value or structural fragment. Present for added,
modified, renamed, moved. |
migrationHint |
string | OPTIONAL | Suggested transform: a FEL expression, "drop", or
"preserve". See §6. |
added — a new item, bind, shape, or other element.
{ "type": "added", "target": "item", "path": "items.contact.phone",
"key": "phone", "impact": "compatible",
"description": "Added optional phone field.",
"after": { "key": "phone", "type": "field", "label": "Phone", "dataType": "phone" } }removed — an element no longer present.
{ "type": "removed", "target": "item", "path": "items.contact.fax",
"key": "fax", "impact": "breaking",
"description": "Removed fax field.",
"before": { "key": "fax", "type": "field", "label": "Fax", "dataType": "phone" },
"migrationHint": "drop" }modified — a property of an existing element changed.
{ "type": "modified", "target": "item", "path": "items.contact.email",
"key": "email", "impact": "cosmetic",
"description": "Updated label from 'E-mail' to 'Email address'.",
"before": { "label": "E-mail" },
"after": { "label": "Email address" } }renamed — item key changed (detected heuristically).
{ "type": "renamed", "target": "item", "path": "items.budget.totalCost",
"key": "totalCost", "impact": "breaking",
"description": "Renamed 'cost' → 'totalCost'.",
"before": { "key": "cost" },
"after": { "key": "totalCost" },
"migrationHint": "$old.cost" }moved — item relocated to a different parent group.
{ "type": "moved", "target": "item", "path": "items.personnel.salary",
"key": "salary", "impact": "compatible",
"description": "Moved from 'budget' group to 'personnel' group.",
"before": { "path": "items.budget.salary" },
"after": { "path": "items.personnel.salary" } }A conformant generator MUST classify each change per the table below. Unlisted changes default to cosmetic.
| Change | Rationale |
|---|---|
| Item removed | Existing responses lose a field. |
| Item key renamed | Stored response keys no longer match. |
dataType changed |
Stored values may be invalid under new type. |
required constraint added to existing field |
Previously valid responses may fail. |
repeat → non-repeat (or vice versa) |
Structural change to stored data. |
itemType changed (e.g., group → field) |
Structural change to stored data. |
| Option removed from closed optionSet | Existing selections become invalid. |
| Change | Rationale |
|---|---|
| Item added (optional) | No impact on existing responses. |
| Item added (required) with default | Fillable from default; no data loss. |
| Option added to optionSet | Existing selections remain valid. |
| New shape added | Presentation only; additive. |
| New bind added | Additive data mapping. |
| Constraint relaxed (e.g., maxLength increased) | Existing data still valid. |
| Item moved between groups (key preserved) | Data intact; layout change only. |
| Change | Rationale |
|---|---|
label changed |
Display-only. |
hint changed |
Display-only. |
help changed |
Display-only. |
description changed |
Display-only. |
| Display order changed within a group | No data impact. |
| Shape property modified (e.g., width) | Presentation-only. |
A conformant changelog generator MUST perform the following steps:
fromVersion and toVersion).key — the
key property is the stable identifier across versions.type: "removed".type: "added".type: "modified" for each differing property.dataType, child structure, and binds, emit
type: "renamed" instead and remove them from the
added/removed sets.type: "moved".semverImpact — take the
maximum impact across all changes: breaking > compatible >
cosmetic → major > minor > patch.Repeat steps 3–7 for binds, shapes,
optionSets, dataSources, and
screeners using their respective identifiers.
A Changelog Document with migrationHint entries on
breaking changes provides sufficient information to
auto-generate a §6.7 migration object.
| Change type | migrationHint |
Generated fieldMap entry |
|---|---|---|
removed |
"drop" |
(omit key — value is discarded) |
removed |
"preserve" |
{ "oldKey": "oldKey" } — carry forward into extension
data |
renamed |
FEL expression (e.g., $old.cost) |
{ "newKey": "$old.cost" } |
modified (dataType change) |
FEL expression (e.g., STRING($old.amount)) |
{ "amount": "STRING($old.amount)" } |
modified (added required + default) |
"preserve" |
{ "field": "$old.field ?? 'default'" } |
Given a Changelog Document C for
fromVersion → toVersion:
{ "fromVersion": C.fromVersion, "fieldMap": {} }.C.changes where impact
is "breaking" and migrationHint is present:
migrationHint is "drop", skip (no
fieldMap entry needed).{ [change.key]: change.migrationHint }
to fieldMap.type is "renamed",
add { [after.key]: migrationHint } to
fieldMap.migrations array.Note: Auto-generated migrations SHOULD be reviewed by a form author before deployment. The
migrationHintis advisory, not normative.
application/vnd.formspec.changelog+json.changelog.json{definitionSlug}-{fromVersion}..{toVersion}.changelog.json
(e.g., grant-application-2.1.0..3.0.0.changelog.json).End of Formspec Changelog Format v1.0.