Swift Evolution Metadata Proposed Changes

The Swift Evolution Dashboard is driven by a JSON file of proposal metadata available at https://download.swift.org/swift-evolution/proposals.json.

In addition, a few other projects in the Swift community are clients of this file.

The current schema has been in place for a number of years now without change. It is missing recent fields like upcoming feature flags, includes outdated fields, and lacks top-level information such as the extraction date and repository SHA the metadata is based on.

This proposal describes intended changes to the metadata schema as well as a transition plan to minimize disruption for clients.

Your feedback on the changes and transition plan are welcome.

All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me by forum message.

Thank you!


Transition Plan

Review Period

There will be a one month review period on the proposed changes to give community members an opportunity to review and provide feedback.

All known client projects of the existing file will be contacted via GitHub issues.

The review period begins today, March 19, 2024 and runs until April, 19, 2024.

Transition Period

After feedback from the review period is incorporated, there will be a 90-day transition period where the existing and new file schemas will be generated.

  • The existing file will continue to be available at:
    https://download.swift.org/swift-evolution/proposals.json

  • The new file will be available at:
    https://download.swift.org/swift-evolution/v1/evolution.json

  • During the transition period, the existing proposals.json file will include added fields but will not remove or change the structure of any existing fields, so as to not break existing clients.

  • The schema version will have a major version of '0' beginning with '0.1.0'

Transition Completion

At the end of the transition period, the existing metadata file no longer be updated.

  • The proposals.json file will be replaced with a permanent file that includes a message and the URL of the new metadata file.

  • Fields that will no longer be part of the schema will be removed.

  • The schema version will reach 1.0.


Proposed Change Overview

1. Top-level JSON Object

The current JSON file proposals.json contains an array of proposal objects at its root.

The biggest structural change would be to make the root a JSON object with a proposals key containing the proposals array.

This allows adding top-level fields such as the extraction date and SHA of the repository commit the metadata is based on.

2. New file name

Name the new file evolution.json to signify the difference from proposals.json.

The new name helps indicate that the file contains metadata beyond just the proposals. It also makes it easy to differentiate between the legacy format and the new format.

3. Individual field changes

There are a number of individual field changes described in the section Detailed Individual Field Changes below.


Detailed Individual Field Changes

Top-Level Field Additions

All top-level fields are new to the schema.

Proposal Metadata

  • proposals: An array of proposal metadata objects that is currently the root of the proposals.json file.

Extraction Details

  • creationDate: An ISO 8601 date string of when the metadata was generated.

  • commit: The swift-evolution repository commit hash used to generate the metadata.

  • toolVersion: The version of the extraction tool that created the file.

  • schemaVersion: The version of the schema used in the file.

Aggregate Values

  • implementationVersions: An array of strings containing a uniqued list of versions found in proposals with “Implemented” status, sorted from lowest to highest version.

    This list will be used by the SE Dashboard to populate the version filters in the user interface. It will ensure the interface is always up to date with the repository, instead of requiring manually checking for proposal changes and updating the script to add new versions.

SE Proposal Metadata Changes

Support multiple release managers

Some evolution proposals have multiple release managers. Currently only the first is included in the metadata.

  • Add releaseManagers as an array of release managers.

  • Remove releaseManager field.

Both keys would be generated during the transition period.

Add support for upcoming feature flags

  • Add an upcomingFeatureFlag field.

Since most proposals do not have an upcoming feature flag, this will be an optional field.

This field consists of a JSON object with two fields:

  • flag: The flag itself

  • enabledInLanguageMode : Language mode version when feature is always enabled. Field is omitted when there is no announced language mode.

  • available : Optional field containing the language release version (e.g. "5.10") when the flag is available, if not the same as the release in which the feature is implemented.

For example:

"upcomingFeatureFlag" : {
  "flag" : "BareSlashRegexLiterals",
  "enabledInLanguageMode" : "6"
}

Changes to errors and warnings

  1. Add a code field

    The code is a unique integer across warnings and errors (i.e. issues). This allows clients to process issues based on a stable value rather than depending on exact wording in the message.

  2. Add a recoverySuggestion field.

    The reported errors and warnings are almost always because of unexpected or malformed Markdown in the proposal.

    Currently some warnings and errors try to cram what might be wrong in the messages field. Adding the field gives a clear distinction between what the problem is and what needs to be done to fix the problem. It also allows for a longer suggestion.

    Recovery suggestions are intended to provide enough information to address the problem and, when practical, to help prevent follow-on problems.

    For example:

    message: “Proposal has no authors”
    recoverySuggestion: “Authors should be listed in a header field:
    * Authors: [PersonName](https://github.com/profileLink).
    - For multiple authors, use a comma separated list.
    - If an author has no GitHub profile, list the name as plain text.
    - Note that only GitHub profile links will be extracted into metadata."

  3. Remove stage field.

    This field can contain one of two values: ‘parse’ or ‘validate’.

    The distinction seems to be whether there is a formatting error that causes information to be unable to be found (parse) or a logical error in the parsed information (validate).

    In practice the value of this field doesn’t provide a useful distinction.

Remove leading dot from status values

  • Replace status value strings like ".accepted" with dot-less strings like "accepted"

    The dot seems to originally been intended to be the same value as in the corresponding case statement in a switch statement. Including the dot does not seem to serve any useful purpose.

Add a diagnostic string for the error state of status

  • Add reason key to status object when status state is 'error'

    This is useful both in encoding, where issues in extracting the status from the proposal Markdown can have a succinct reason for the failure, and in decoding where, for example, a Swift enum modeling status can decode an unknown new state value as an error with the unknown status noted in the reason string.

Remove unused fields from TrackingBug

  • Remove assignee, radar, resolution, status, title, and updated fields

    One intent of the legacy format was to provide metadata about the status of all tracking bugs.

    The primary fields id and link are parsed from the proposal itself.

    The secondary fields assignee, radar, resolution, status, title, and updated were all populated by a second pass of fetching from JIRA.

    This functionality was not updated when the Swift project moved from JIRA to GitHub Issues in spring 2022. For the past two years, all fields except for id and link have been empty.

    The SE Dashboard does not use the additional fields and they have no known clients. There do not seem to be any requests in the past two years from the community asking for these fields to be populated.

    This change would keep the primary id and link fields that are currently extracted from the source proposal files and remove the secondary fields.

    Since the id and link for each issue will remain, any client of the metadata file can use the GitHub API to get additional details as desired.

Add support for proposal review discussions

  • Add a discussions field.

In the proposal file, the header field is 'Review' but is defined as the discussion about the proposal. For clarity in the metadata, the field name will be discussions.

This field is an array of JSON objects with two fields:

  • name: The name of the discussion "pitch" "review" "acceptance"

  • link : The link to the discussion.

For example:

"discussions" : [
  {
    "name" : "pitch",
    "link" : "https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799"
  },
  {
    "name" : "review",
    "link" : "https://forums.swift.org/t/se-0434-usability-of-global-actor-isolated-types/71187"
  },
]

Discussions about this addition:
original proposal post
additional commentary

Add support for previous proposal IDs

  • Add a previousProposalIDs field.

A proposal can have an optional list of proposals that form a 'line of succession'. For example, part of a previous proposal may be extracted and pitched as a separate proposal. The criteria is described in more detail in the Swift Evolution proposal template.

This field is an optional array of proposal ID strings extracted from the 'Previous Proposal' header field.

Discussions about this addition:
original proposal post
additional commentary


Schema Summary

Top level object:

{
   "creationDate": String, // (ISO-8601 date format)
   "commit": String,
   "implementedVersions": [String],
   "proposals": [Proposal],
   "schemaVersion": String,
   "toolVersion": String
}

Proposal object:

{
  "id": String, // SE-NNNN, e.g. "SE-0147"
  "title": String,
  "summary": String,
  "link": String,
  "sha": String, // SHA of the proposal's Markdown file's latest update
  "authors": [ // [Proposal.Person]
    {
      "name": String,
      "link": String // warns on absence
    }
  ],
  "reviewManagers": [ // [Proposal.Person]
    {
      "name": String,
      "link": String // warns on absence
    }
  ],
  "status": {  // Proposal.Status
      "state": String, // Status state name, e.g. "implemented"
      "start": String?, // ISO 8601 date string, key only present for status states with dates
      "end": String?, // ISO 8601 date string, same as above
      "version": String?, // swift version, e.g. "3.0.2", present only for 'implemented' state
      "reason": String? // reason for error state, present only for 'error' state
    }
  },
  "discussions": [ // [Proposal.Discussion] A list of the related proposal discussions
    {
      "name": String, // "Pitch", "Review", "Acceptance", ...
      "link": String
    }
  ],
  "trackingBugs": [ // [Proposal.TrackingBug]?
    {
      "id": String,
      "link": String,
    }
  ],
  "implementation": [ // [Proposal.Implementation]?
    {
      "type": String, // "commit" | "pull"
      "account": String,
      "repository": String,
      "id": String
    }
  ],
  "previousProposalIDs": [String]?, // IDs of proposals that form a line of succession to this proposal
  "upcomingFeatureFlag" : { // [Proposal.UpcomingFeatureFlag]?
    "flag" : String
    "enabledInLanguageMode" : String?, // Language mode version when feature is always enabled. Omitted when there is no announced language mode
    "available" : String?, // Optional field containing the language release version when the flag is available, if not the same as the release in which the feature is implemented.

  },
  "warnings": [  // [Proposal.Issue]?, key omitted if no values
    {
      "code": Int, // unique identifier across warnings and errors
      "kind": "warning", // differentiates this from an error
      "message": String, // human-readable description of what's wrong
      "recoverySuggestion": String // human-readable description of how to address the issue
    }
  ],
  "errors": [  // [Proposal.Issue]?, key omitted if no values
    {
      "code": Int, // unique identifier across warnings and errors
      "kind": "error", // differentiates this from an error
      "message": String, // human-readable description of what's wrong
      "recoverySuggestion": String // human-readable description of how to address the issue
    }
  ]
}

Revisions:

3/20/24

  • Changed top-level field names
    • From sha to commit
    • From extractionDate to creationDate

3/23/24

  • Removed section on using default Codable structure for status object
  • Added an optional reason field for error status
  • Changed untilLanguageMode to enabledInLanguageMode to match the LSG's preferred naming.

4/1/24

  • Fix typo in domain name of download.swift.org.

4/4/24

  • Updated proposed fields for upcomingFeatureFlag
    • Add optional available field for flags that are available after proposal implementation version
    • Make enabledInLanguageMode field optional

4/12/24

  • Add proposed field for discussions
  • Add proposed field for previousProposalIDs
23 Likes

Thank you for doing a transition period, giving other clients time to adapt to the changes. As the maintainer of an Alfred workflow for querying Swift Evolution proposals, I really appreciate this. Special thanks to @James_Dempsey for proactively searching for projects on GitHub that use the existing JSON file and opening an issue to alert maintainers about the upcoming change.

As for the proposed changes, +1 on including new metadata such as upcoming feature flags.

If the proposed field names are up for bikeshedding, I don't love a few of them:

  • "extractionDate": This focuses on the process by which this file is generated. I don't think this is particularly relevant to users of the file. Maybe something like "creationDate" or "updateDate" would be better?
  • "sha" is maybe too specific about a particular hashing algorithm, which isn't super relevant. How about "commit"?

But these are very minor complaints. I'm not super strongly against the proposed field names.

7 Likes

Thank you @ole. It seemed like the best approach to minimize avoidable breakage and also ask for feedback from the people most likely to be interested.

Thank you also for your feedback.

Both field names you mention are ones where I went back and forth between what’s in the proposal and what you suggested. So your feedback is definitely appreciated.

I think creationDate and commit would be good changes.

3 Likes

As maintainer of Vapor's Penny, I also thank you both for the valuable changes and also for proactively notifying us the maintainers.

Reading through your proposed changes, I agree with all of them for the most part.

One concern of mine is:

Based on my experience that won't really be a good idea to use the default implementation like that anyway. Because adding a new enum case will fail the whole decoding process, which is suboptimal. Is there something we can do about that? Perhaps, basically making it a "frozen" enum with publicly-known cases?
I do notice that it's be suboptimal to have constraints of a "frozen" enum, but not sure what will be a better solution. I'm open to suggestions.

About @ole's points, I do agree with what he suggested as well.

Some side-notes:

As I've mentioned on the swift.org repository on GitHub in the past, I wish swift-evolution had CI. Not a great deal but it'd be nice for the proposals-endpoint-users to be able to have more trust on the endpoint, as well as the fact that it makes contributing to the repository easier too by making sure the proposals are already in the correct form before each merge.

And about that ... what is the situation of the proposals.json generator script?
If i were to guess i'd imagine the current script is written in Python, so more specifically, i'm asking if it's public code, and if it's already written using swift-markdown. It'd be useful for the public to have access to the code so they can contribute.

1 Like

Thank you for your feedback @MahdiBM.

Regarding the status field, I believe your assessment is correct.

At a high level, if the Swift Evolution process adds a new status, clients would generally need to update to handle the new status value regardless of the client language. (The evolution dashboard JavaScript maps the status values to human readable strings, for example.)

In a Swift client, it is natural to model the status as an enum.

So, for a Swift client, it would be desirable to have some indication there is a new status value that needs to be handled, but something less severe than having the entire decode fail.

The metadata file generator already defines an "error" status value that is not an actual status in the evolution process.

A Swift client could add a default case that returns the error status value if no other cases match.

That would allow decoding to succeed, but give clients a way to check for status errors.

It would probably be useful for the "error" status value to have an optional associated "reason" string that could contain the reason that status was used, including the name of the unrecognized status: "Found unrecognized status 'rejectedWithExtremePrejudice''". This would also be useful any time the "error" status is used to provide additional diagnostic information.

It is also unfortunate that the default encoding of enums uses the enum value itself as part of the encoded schema. There seems to be no way to get a decoder to report keys and values that are not already defined as coding keys. So, with the default encoding of enums, there does not seem to be any way, even in custom decode methods, to detect new enum values and return an "error" case.

So, that leaves us with the original schema, with the proposed addition of an optional reason field to be used with the error status.

  "status": {
      "state": String, // // Proposal.Status case name, e.g. "implemented" 
      "start": String?, // ISO 8601 date string, key only present for status states with dates
      "end": String?, // ISO 8601 date string, same as above
      "version": String?, // swift version number, e.g. "3.0.2"
      "reason": String?, // optional reason for error status
    }

I also think it would make sense to make the full set of possible status values to be public.

And in terms of swift-evolution pre-merge metadata validation and having the metadata generating tool be public, we are on the same page.

Again, thank you for your feedback!

1 Like

I've updated the proposal to incorporate feedback to date.

2 Likes

Have you decided what versioning strategy you're going to use? I'd recommend at least a major.minor form to separate breaking and non breaking changes.

Is there room in the proposal object to include links to the first toolchains for each platform where the feature is available? Links to release versions, as well as versions of Xcode that integrate the feature, may also be useful.

2 Likes

I'm working on a document regarding versioning, but for the schema version, my thoughts were along the lines you suggest.

A major version bump would mean breaking changes such as removal of fields and changes to the structure. A minor version change would be non-breaking, like adding fields.

The intent is for the original proposal document to be a self-contained source of truth for the proposal metadata.

In terms of links to the first toolchains where the feature is available, do you mean available while it is still proposed / under review?

Once a proposal is implemented, the Swift version is included in the metadata, so a client like the SE Dashboard could potentially construct the appropriate link to the release version.

I guess I go with the latest development toolchain its in during the proposal, or if merged, the mainline toolchain. Then switch to the version toolchains it officially becomes part of until a release, where it would be the Swift and Xcode version

2 Likes

Thanks @Jon_Shier.

The reason I ask is that the Implementation header in the SE proposal was always a link to one or more pr/commits that contained the implementation. That is what is currently extracted. Now sometimes that header contains some explanation of how to use the implementation (e.g. "On main using experimental feature flag ProposedFeature").

I was thinking that after this initial transition, it would make sense to work with the LSG to standardize what appears in that field or if possibly 'how to use the pre-release feature' which a toolchain link might be a part of should be separate than links to the code that implements the feature.

Spending more time helping update upcoming feature flags in the source proposals, I have updated the proposal for upcomingFeatureField:

First, typically an upcoming feature flag (UFF) is available in the same release that implements the proposal. On occasion, the UFF is not available until a later release. (For example, SE-0352 was implemented in 5.7 but the UFF is not available until 6.0.)

For these cases, the upcomingFeatureField metadata will include an optional available field.

Second, the enabledInLanguageMode field which will often not have a value, will also be an optional field.

Making both optional matches closely with the Upcoming Feature Flag header in the source proposals, where these additional annotations are often not required or present.

It’s awesome to see this discussion about something I’ve been looking at for a while when building Proposal Monitor, the app I just announced on Community Showcase. Recently I’ve noticed a few meaningful changes that had impact in the app and agreeing on a schema is the best way to avoid those problems.

I believe the proposed schema is great and improves a lot over the original.

Nonetheless, I’d like to know more about the title and summary fields. Today they contain the markdown version of their contents. Are they going to keep being like this? Should we formalize this somewhere? (Considering my use case I prefer it to keep storing the markdown content)

And I’m not sure if this question fits here, but I’d like to know if changes to the summary parsing could be discussed as some proposals simply have no summary as of now (SE-0425 is an example).

1 Like

Thank you for the feedback @Victor_Martins and congratulations on launching your app!

This proposal focuses on schema changes and transitioning to the new schema with the thought that improving individual field content could happen without needing changes to the schema itself.

But yes, both things you mention are open to discussion, separate from the schema changes. Please feel free to start threads here in Swift Website.

I also believe summary parsing could be improved. And it would be good to get feedback regarding markdown versus plain text. (The summary field is an interesting case as the other fields appear on the Swift Evolution dashboard, but the summary is not currently used.)

I also appreciate your feedback on the proposed changes. My hope is that this schema can be used a long while without requiring breaking changes.

Thank you! :slight_smile:

Sorry to insist, but I'd just like to mention that I believe that if the community were to prefer having both a field containing markdown content and a field containing the "raw" content, this would probably result in us having additional fields to accommodate. That's why I thought it could make sense to bring up this discussion on here. But, again, keeping it as it is right now (containing markdown) is totally fine for me. I'll keep an eye if someone proposes a change in another thread like you mentioned.

Yeah! And it's not really that much standardized on the proposals themselves. Some of them start with an introduction section, some start with a paragraph and then they write the introduction section, ...

Sorry, I misunderstood what you meant. Potentially adding additional fields is definitely part of the schema discussion.

I think the main downside of providing multiple representations is it provides nearly duplicated data for those fields, especially when one of those fields is the summary, which is already typically the largest amount of data per proposal.

I believe the markdown value provides the most flexibility for a client without duplication.

For example a Swift client can:

  1. Use the string as-is. (For example display titles with backticks to indicate code voice)

  2. Create an attributed string with AttributedString(markdown: value)

  3. Get the content string with NSAttributedString(markdown: value).string
    (I think the content string is what you mean by the "raw" content.)

There may be use cases I am missing (we have a relatively small group of known clients of this file).

But I believe this provides a good trade-off between file size and flexibility for clients.

(Note that I use NSAttributedString in the example for brevity because it has a convenience method. A similar convenience can be written with a few lines using the CharacterView of AttributedString.characters.)

Yeah, I completely agree with everything you said.

And if there was ever a relevant use case, we could consider having a different URL for each variant (raw or markdown) instead of changing the schema again and having to duplicate most of the content, as you said. I admit I was a bit worried about the conversion from markdown to its content string on other platforms, but I'm probably overthinking since we haven't even heard of those use cases.

1 Like

Yes and Markdown is generally well supported across various platforms and languages.

I have been preparing for the beginning of the transition and have been wondering whether the proposed recoverySuggestion field in errors and warnings could be shortened to suggestion without losing clarity.

The purpose of this field is to suggest how the reported issue can be fixed in the original Markdown, to make it easier to fix issues with missing or mis-formatted fields.

On the details' screen of a proposal on Proposal Monitor I show previous related proposals and the history of discussions. To get this information I download the original markdown from the full proposal and extract the values from the header fields that currently aren't present in the JSON.

For my use case, it would be interesting to have these fields on the schema. So, I propose we add the following fields to each proposal:

{
  "id": String,
  "title": String,
  ...
  "previousProposalIDs": [String]?,
  "discussions": [ // A list of the related discussions in chronological order
    {
      "title": String, // "Pitch", "Review", "Acceptance", ...
      "link": String
    }
  ]
}

Alternatives considered for the first field:

  • We could name it previouslyRelatedProposalIDs
  • We could name it relatedProposalIDs and mention both the proposal A in the related proposals list of the proposal B and vice-versa, instead of having only the new proposals pointing at the older ones

Alternatives considered for the second field:

  • We could name it relatedDiscussions or discussionHistory
1 Like

Sorry if I'm just blind but is there documentation of the new (or old) schema somewhere?

For example, I have a question about the semantics of the "link" field in the proposals. It seems to be a relative link based on https://github.com/apple/swift-evolution/blob/main/proposals/. The link's base is not mentioned in the file. Maybe there could be a top-level "extractionRoot" field or similar?

I'm currently only toying around with the metadata. I'd love if there was a basic Swift package to parse the file into proper types. This could also be a great place to find documentation.

1 Like