# Forkcast EIP feature This feature proposal will implement an EIP page at `forkcast.org/eips/<eip-number>`, and extend the data type in `eip.ts` with a `statusHistory` field so that stakeholders can view what the inclusion status is for an EIP and how it changed in relation to ACD calls. In a PR following the EIP page feature, we can apply `@deprecated` to the `status` field in `ForkRelationship`, and refactor status references to instead use `statusHistory.at(-1).status`. One major consideration of this feature is intentionally leaving the data type flexible, so if we need to make enforcements we can do that at build time in `compile-eips.mjs` or in the presentation layer when rendering the EIP data. For example: - What if a maintainer SFIs an EIP in two fork relationships? -> we could check this at build time instead of enforcing a constraint/complexity into the data type - What if we want a particular order in the presentation layer; eg. always show DFI before SFI (because you can DFI and SFI an EIP in two forks at the same time) -> we can enforce this in the presentation layer, it's a constant time operation While this PR is only focused on adding a history field to minimize complexity in the initial PR, it keeps in mind that Forkcast aims to paint more color on the context of an EIP (and not just show the contents of an EIP, which is better served by an outbound link to `eips.ethereum.org`). In the future we should address the following scenarios/stakeholders: - How do client devs feel about this EIP? - How does this EIP align with north star(s)? - Who is the champion? - Who does this EIP impact? - What fork will this EIP come in? - What are the most talked about EIPs right now? - What are the most impactful EIPs right now? - What is the testing status or testing complexity of an EIP? This feature keeps in mind extensibility / flexibility, for example we may or may not have an additional page in the future which presents all EIPs that Forkcast has context on in a single page, segregated by fork, etc. This feature is only impacting the `eip.ts` data type and will compose well with future features like that. This feature satisfies the following scenarios in the **future** if they are well designed and thought out (but out of scope for this PR): - Median client developer is heads down building and misses the last two calls, they want to know: "what EIPs did I miss?", "did any EIP status change?". These types of changes revolve around the call, so the call page would be the best place to display changes to an EIP. - Solution: On the **call page**, include an EIP context section. This will show any changes to the EIP (it would result in a change to the EIP context struct described in `eip.ts`). - A community member comes to Forkcast just to find the status of an EIP: - Solution: They utilize a search feature for the EIP and it lands on the EIP page where they have context, or they view an aggregate EIP page. ## Proposal The feature will not introduce a breaking change to the `eip.ts` data type (or relying code), but add a new, temporarily optional `statusHistory: ForkStatusEvent[]` field to `ForkRelationship`. It will also add a `@deprecated` tag to the existing `status` field. Getting the current status would effectively be a `statusHistory.at(-1)`, but we can introduce a helper method for that. The scope of the feature will be: - Add a route for the EIP page `/eips/:id` -> EipPage - Extend the `eip.ts` data type to answer when/what an EIP status changed, with a new `statusHistory` array EipPage will fetch the requested EIP context from `eips.json` using the `id`, and that may include the new `statusHistory` array. It will use this array to show a timeline feature of how an EIP has changed over time within each fork relationship. We can render most of the EIP context using the existing `EipCard` layout and extending it, if it looks good; otherwise create another layout that is more suitable for the page which could compose with parts of `EipCard`. Let's look at the current `ForkRelationship` data type to see what will change: This: ``` export interface ForkRelationship { forkName: string; status: string; isHeadliner?: boolean; wasHeadlinerCandidate?: boolean; headlinerDiscussionLink?: string; layer?: string; champion?: Champion; } ``` Will become this: ``` // NEW INTERFACE export interface ForkStatusEvent { status: 'Proposed' | 'Considered' | 'Scheduled' | 'Declined' | 'Included'; call?: `${'acdc' | 'acde' | 'acdt'}/${number}`; reason?: string; } export interface ForkRelationship { forkName: string; status: string; // NEW FIELD statusHistory?: ForkStatusEvent[]; // ordered oldest -> newest isHeadliner?: boolean; wasHeadlinerCandidate?: boolean; headlinerDiscussionLink?: string; layer?: string; champion?: Champion; } ``` Finally after introducing the new EipPage, we should also ensure we have OpenGraph data for social sharing / SEO: - Meta title - Meta description - Possibly a dynamic OpenGraph image that shows the above, along with the EIP status ## Considerations - What is the complexity? - The complexity is only extending the data type in `calls.ts` without a breaking change. We are also not increasing any LOC in existing components like EipCard, but composing it into the new EipPage with new history features. All components will not be growing LOC / complexity in other components. - What is the maintainability? - The maintainability is something to consider. The main one, is ACD coordinations / Forkcast maintainers will now need to learn about a new primitive: `statusHistory[]`. I believe this is unavoidable if we want to encode history data into the EIP. However, over time, we can utilize the transcript + AI in a GitHub actions workflow to open PRs that will automatically add this information to the statusHistory array. - What is the product focus? - The main product focus is for people asking "what is the status of this EIP"? It's not around rendering all the details of an EIP, that is better served in eips.ethereum.org; instead, Forkcast is painting context around the EIP especially in relation to ACD calls. It answers, at first, the question of "What is the status of this EIP and how did it change over time, and when (where can I learn more)?" - What to do we about logical errors? - As stated in the proposal the idea is to make the change as simple as possible and enforce logical constraints at build time, and presentation preferences at render time. I feel it is better to do this instead of making the `statusHistory[]` data type change more complex. We can also check logical constraints related to other data fields like "headliner" status (eg. an EIP cannot be selected as a headliner but also DFI'd in the same fork). - Extensibility? - This feature keeps in mind that we show EIP data all over Forkcast (in the Glamsterdam tier selector, and also in the fork planning page). We can utilize this data type to show a small button or badge that allows you to click further into the EIP and get context, either on those pages or to get to the EipPage. - Why is `call` field optional? - The reasoning for this is so we allow for backwards compatibility when migrating existing `status` into the new `statusHistory`. The past EIPs we have indexed with a status don't have a call reference, and backfilling that may be challenging (it is possible with parsing the transcripts + careful manual review). So this allows us to do a backwards compatible upgrade and not introduce two `status` fields that could diverge. The **tradeoff** is that there is a responsibility on maintainers to do their best in adding the `call` reference when adding new `statusHistory` events. I believe this is an acceptable tradeoff and something we can check for. - What happens if a maintainer DFIs in one fork but forgets to SFI in another fork? - This shouldn't really matter, some burden is on the maintainers, ideally in the future this can be figured out from the transcript and if it's wrong, a PR can be opened to correct it. Some responsibility has to be put on the agent that updates the `statusHistory`. - Is this data type / format optimal? What about the performance? Think about how we will use this in practice in the future. If we load up a calls page, can we with good performance and scale in the future pull in the EIPs that are "mentioned in this call" using the type/number (it would be a `path` key)? - It seems like YES and that we can also at build time statically generate it and not even pull it in at runtime when loading the route. - Why not introduce a `date` field in ForkStatusEvent? What if status events are put in the wrong order by maintainers? - We already have a `date` field in the `call` reference and prefer to only have one source of truth so that they don't diverge. We don't put explicit ordering constraints for design reasons mentioned previously (build time checks, including over call `date` fields are possible; some responsibility is on the maintainer). - What if the maintainer doesn't enter in the callPath correctly, eg. `acdc/12345` (a number that doesn't exist)? What if we change calls in the future eg. `acdf`? - Again we can check for this at build time if needed, which shouldn't be hard, but also some accepted responsibility on the maintainer here. - If we change calls in the future, then the `calls.ts` data type will change. We could couple to the `calls.ts` data type, so changes to it would propagate in the future. I think during implementation sharing a type from `calls.ts` would be a good idea. - What if we introduce new inclusion status's / call types? - We can pull some of the `call` data type under `types` in a future PR, and also reuse inclusion status as another type, and share that - What if in the future we want to support additional changes to context around an EIP that is not just the inclusion status? For example, changes to an EIP champion, headliner changes, etc.? - In the future we can make a more generic `ForkEvent`: ``` type ForkEvent = | { kind: 'inclusion-status'; status: ForkInclusionStatus; call?: CallId; reason?: string } | { kind: 'headliner'; isHeadliner: boolean; call?: CallId; reason?: string } ``` - For now, it seems like unnecessary complexity. It is not an abstraction I feel like we need to maintain now for a v1 feature. - How can we show on a Call page for example, the status change in a performant way ("what changed in this call?")? - We can pre-compute the change at build time. - This is important as the dataset grows and answers the two queries: "how did an EIP change over time" + "what EIPs changed in this call" - The basic architectural idea stands; instead of encoding complexity into the data type, we can do build time optimizations / static generation, if we don't want to filter the entire EIP list when loading a call (simplicity in the data type gives us flexibility for ideas like this) - Should we include more expressivity in the data type? Would it make it easier for maintainers to add changes to EIP statuses? - I believe no, for reasons as stated above. The statusHistory array has all required information: how did the status of an EIP in a fork change over time? Additionally, this approach is maximally simple. It is literally just extended the current `status` with an array of `status`, and an optional `call` + `reasoning`. If we introduce more expressivity into the data type for example `lastStatus`, and some sense of inter-fork directionality (changes from SFId to DFId in Fork A, and CFId to SFId in Fork B), then it would make the abstraction more complex. This is maximally simple for maintainers but also pushes complexity into build time / presentation layer. ## Invariants Here are some of the main invariants / "unit tests", to ensure that this feature works as expected. Long story short, we cannot impose ordering on the data type, and we can enforce these constraints if we find it an issue later on, at build time. **Valid scenarios:** - (proposed | considered | declined) in several forks -> can check at build time **Invalid scenarios:** - (scheduled | included) in several forks -> can check at build time - duplicate: `scheduled` in one call, and `scheduled` again in a subsequent one -> can check at build time - events after `included`; this is a terminal state, but again we can check at build time - maintainer puts forks out of order -> can enforce when `call` becomes required (with `date` field) - fork chronology: if Fork A precedes fork B, and an EIP reaches terminus in Fork A (included), it shouldn't have status updates in fork B -> can check at build time - call must exist if referenced -> can check at build time ## Follow ups - To minimize complexity of the feature we can do the deprecation in a follow up PR - Pull out some useful `Call` data type from `calls.ts` under `/data` and into `/types` and then use it in our `call` field. For example: `export type CallType = 'acdc' | 'acde' | 'acdt'; export type CallId = ${CallType}/${number};` ## Implementation PR I wrote a quick implementation PR here. It's mainly a mockup but I think the direction is good and easy to review. It shows the EIP status history on all EIPs when viewing a fork, with a link to "view more details" that links to the EIP page for that EIP. Implementation here: https://github.com/dionysuzx/forkcast/pull/4 ![image](https://hackmd.io/_uploads/S1s4ej4Mbx.png) ## Additional ideas - Considering renaming `status` to `inclusionStatus` so to not be confused with EIP status - Can we utilize anything from here or collab? -> https://eip.tools/ - Should we do backlink references from Magicians/eips.ethereum.org back into Forkcast for context - Consider the more generic event type and give it a try and see how bad the maintainer UX is (ForkEvent vs ForkStatusEvent) -> this is an important one but I believe simplicity wins here, and better to deprecate the ForkStatusEvent type in favor of a new field if we need it in the future - **Serious contender**: instead of `statusHistory` on the EIP data (`.json`), put `eipDecisions` on the call data (`calls.ts`). One issue is this makes `call` required, and we're not sure yet how well we can backfill which call every EIP inclusion status was determined in. It turns `call` from an optional to a required field, and also increases a cognitive shift for maintainers; so I tentatively prefer `statusHistory`, for now. If we can convert `call` to a required field after `statusHistory`, then I think moving to a Call centric data model would be worth re-considering, if a change necessitates it. You could use the existing `status` field for EIPs that do not have a known associated call, but this breaks cohesion. ## Questions for wolovim - What is the fork window for Forkcast now, and do you see it changing? Right now we have Pectra (Active), Fusaka (Upcoming), and Glamsterdam (Planning)--how about another future fork Hogota? How far ahead and previous do we go? - What do you think of `statusHistory` on EIP data objects compared to `eipDecisions` on Call data objects?