Repository navigation
feat!: forge reads native extension keys only (FORGE-1) - #73
Open
Swimburger wants to merge 2 commits into
Open
Swimburger wants to merge 2 commits into
Swimburger wants to merge 2 commits into
Conversation
This was referenced Oct 28, 2026
Swimburger
force-pushed
the
forge-native-keys-only
branch
from
October 28, 2026 01:01
491beec to
d0ecf1f
Compare
…writes the rest `forge` reads a native `x-forge-*` vocabulary and nothing else. The extensions other tools write (`x-fern-*`, `x-stainless-skip`, `x-sensitive`) are ordinary extensions to it, and a new command, `forge normalize`, rewrites them into the native ones, so the spec that is produced is itself what gets transformed. FORGE-1. The native vocabulary: `x-forge-method-name`, `x-forge-group-name` (a dotted string or an array), `x-forge-skip`, `x-forge-availability`, `x-forge-model-name`, `x-forge-property-name`, `x-forge-parameter-name` and `x-forge-sensitive`, kept by `src/metadata/` per kind of object, alongside `x-forge-hidden`, `x-forge-require-confirmation`, `x-forge-aliases` and `x-forge-commands`. The reader asks it for a key and finds one written twice; there is no fallback and no ranking between keys. `forge normalize [--leave-old-extensions] [--check] [-v] <INPUT>...` takes files, folders (every .json file below them, except hidden ones and those in a node_modules), glob patterns, or - for standard input to standard output, and rewrites them in place. - It edits the bytes of the document and does not write it again, so whitespace, the order of keys and the text of every number stay as they were, and a diff is only the keys. It walks the document once with no tree, and checks it to be JSON on a second thread. - A key is rewritten only at the kind of object `forge` reads its native key at, so it never gets a meaning it did not have. The tables of other tools' keys are `src/normalize/vendor/`, per level, the only code that names them; a boundary test enforces it. - A native key is never replaced: when a document says the same thing twice, the native key is kept, and the other is dropped, or reported if it says something else. - A group path that is an array becomes the names joined by dots, unless a name has a dot in it. What no native key says, such as a skip that is a list of targets, is left as it is written. - What it cannot rewrite, a key written twice or a value that cannot be read, is left and warned about, with where a parser would stop. - Files are replaced by writing a new one beside them and renaming it, or in place where that is not possible, as for a hard link. A file with more than one name is rewritten once, and a link stays a link. - `--check` writes nothing and fails if anything would change. RFC 0001 describes the design, and AGENTS.md says that compatibility with other tools' extensions is sectioned off in `src/normalize/vendor/`. Breaking: a document written with other tools' keys builds only what its native keys say until it has been through `forge normalize`.
…or now Until the release that makes `forge normalize` a step to run first, `forge` runs the same rewrite on the bytes it reads, before it parses them, so a document that has not been through `forge normalize` builds what it did: the released aggregate writes the same 3,243 files of `--target ts`, byte for byte, without the step. FORGE-1. It is temporary, and only the binary does it: the engine in `src/` reads native keys and nothing else. - It is the default rewrite, so the keys it rewrites are not kept, as `forge` did not hand them on to plugins before. - It says nothing of what it leaves, as `forge` said nothing of the document before. A key written twice is an error in the words `forge` used when it read those keys itself, down to the line and column: "duplicate field `x-fern-sdk-method-name` at line 7 column 30". A value that cannot be read is left in place, and `forge` says it as it reads it. - An error `forge` finds later in the document gives the column of the document that was rewritten, which differs from the one that was read where a key was renamed earlier on the same line. The line is the same. Cost, on the 28 MB released aggregate, best of 30: the rewrite is 17.6 ms when it has 11,714 keys to rewrite and 14.4 ms when it has none, next to 41.6 ms to parse into a `Forge`. The whole `--target ts` run goes from about 252 ms to 262 ms for a normalized document and to 270 ms for a raw one, which is 7%.
Swimburger
force-pushed
the
forge-native-keys-only
branch
from
October 28, 2026 01:08
d0ecf1f to
fe43db8
Compare
Swimburger
marked this pull request as ready for review
October 28, 2026 01:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
forgereads the nativex-forge-*keys and nothing else, and a new command,forge normalize, rewrites the extensions other tools write (x-fern-*,x-stainless-skip,x-sensitive) into them. FORGE-1.This is one PR against
main: it carries the native vocabulary of RFC 0001 andforge normalize, so it does not depend on any other PR.What
src/metadata/per kind of object, described inrfcs/0001-metadata-resolution-layer.md:x-forge-method-name,x-forge-group-name(a dotted string or an array),x-forge-skip,x-forge-availability,x-forge-model-name,x-forge-property-name,x-forge-parameter-nameandx-forge-sensitive.forge normalizerewrites the keys of other tools into those, in place:forgereads its native key at.src/normalize/vendor/, per level, and are the only code that names them. A boundary test (tests/boundary.rs) enforces it, andAGENTS.mdsays that compatibility is sectioned off there.Two commits:
c012837: all of the above. The engine has no fallback, no tiers and nocompatfeature.fe43db8: until the release that makesforge normalizea step to run first, theforgecommand runs the same rewrite in memory before it parses. Temporary, and only the binary does it.Breaking
A document written with other tools' keys builds only what its native keys say, unless it has been through
forge normalize. The in-memory rewrite keeps theforgecommand working for now; Rust code that parses aForgeitself does not get it.Performance
forge normalizeon the 28 MB released aggregate, the whole process, best of 20:--leave-old-extensions, first run--checkThe in-memory rewrite plus the parse, same document, best of 30:
ForgeThe whole
forge --target tsrun goes from about 252 ms to 262 ms for a normalized document and to 270 ms for a raw one, which is 7%. The rewrite walks the document once with no tree, and checks it to be JSON on a second thread.Behavior to look at
x-sensitiveis not read by the engine.forge normalizerewrites it tox-forge-sensitive. It is not called deprecated, since other systems write it, and--leave-old-extensionskeeps it for them. @srikrsna: if the engine should keep reading it, that brings back some handling of two keys for one thing.normalizeis lenient, and theforgecommand is not. A key written twice, or a value that cannot be read, is left and warned about byforge normalize. Theforgecommand fails on a key written twice with the error it always gave,duplicate field `x-fern-ignore` at line 1 column 174, and leaves a value that cannot be read for the engine to say.forgefinds later in the document says the column of the rewritten document, which differs from the one read where a key was renamed earlier on the same line. The line is the same. It goes with the in-memory rewrite.Verification
On the released aggregate, against the SDK the engine wrote before the change:
--target tswrites all 3,243 files, byte for byte, from the raw document (through the in-memory rewrite), from the document afterforge normalizein either mode, and throughcat spec.json | forge normalize - | forge - --target ts. Without the rewrite, the raw document writes 7 files, so the check can tell.FORGE_SPEC=openapi.forge.json cargo test --release --test normalize aggregate -- --ignoredbuilds the same model in both modes, with no conflicts.-D warnings) with all features and with none, and its tests (613 and 619). The final tree also compiles forx86_64-pc-windows-msvc.Follow-up
Remove the in-memory rewrite in the release that makes
forge normalizerequired: revertfe43db8.