Close JSON Schema gap-analysis findings - #169
Conversation
| origins: &mut BTreeMap<PathBuf, PathBuf>, | ||
| path: PathBuf, | ||
| contents: String, | ||
| origin: PathBuf, |
There was a problem hiding this comment.
I don't think we should represent origin as a pathbuf if it often isn't a real path.
| @@ -59,6 +59,31 @@ impl GeneratedFiles { | |||
| } | |||
| } | |||
|
|
|||
| /// Inserts one generated file while retaining enough provenance for P7.2's | |||
There was a problem hiding this comment.
I don't like referencing the json principles in the generic code. What are we actually trying to accomplish here?
| @@ -808,6 +826,38 @@ fn insert_generated_file( | |||
| Ok(()) | |||
| } | |||
|
|
|||
| fn insert_generated_file_with_origin( | |||
There was a problem hiding this comment.
Why is this different from the one defined in mod.rs?
| @@ -85,11 +87,23 @@ fn generate_tree( | |||
| ) -> Result<GeneratedFiles> { | |||
| let model_hoists = tree_model_hoists(branch)?; | |||
| let mut files = BTreeMap::new(); | |||
| let mut origins = BTreeMap::new(); | |||
There was a problem hiding this comment.
I don't really understand the whole origins thing here. We're adding a lot of context, best I can tell is so that we don't add the same file twice? But because of different sources or something?
| /// input leaf/support fragment, or a descriptive virtual path for a fixed | ||
| /// generator-owned artifact. | ||
| pub(crate) fn insert_generated_file_with_origin( | ||
| files: &mut BTreeMap<PathBuf, String>, |
There was a problem hiding this comment.
If I've come to grasp the point here correctly, I don't think a separate origin map makes sense. Why not just store the source of the file as part of the file itself?
files: &mut BTreeMap<PathBuf, String>, into files: &mut BTreeMap<PathBuf, (String, String)>, though probably with a struct instead.
There was a problem hiding this comment.
Potentially there's enough complexity there that really it is a new object with some helpers for FileMap or whatever. Assuming the same thing is shared between langs, which it's unclear to me is the case, or why this is defined in the root mod.
| path, | ||
| contents, | ||
| source, | ||
| "rename one input file or directory so the generated Python paths differ", |
There was a problem hiding this comment.
I don't see how it knows this remedy in this location, is this just sorta the default?
| /// strings/templates and regex literals, so this small lexer can distinguish a | ||
| /// real `Long` type occurrence without treating the exact const string | ||
| /// `"Long"` (or a pattern containing it) as a package dependency. | ||
| fn contains_identifier_outside_literals(source: &str, identifier: &str) -> bool { |
There was a problem hiding this comment.
This whole thing seems pretty sketchy. Some kind of hand built parser for the typescript?
There was a problem hiding this comment.
You're right. I missed this while skimming the generated code. I got used to not paying much attention to the code in this project.
| @@ -3470,6 +3619,7 @@ fn render_support_module(support_source: &str) -> String { | |||
| fn render_index_module( | |||
| services: &[RenderedService<'_>], | |||
| model_type_names: &BTreeSet<String>, | |||
| has_json_runtime_module: bool, | |||
There was a problem hiding this comment.
This seems like an abstraction leak, although one that partially already existed. Why does this generator know about Violation?
There was a problem hiding this comment.
It also knows about Long, which is proto / WIT specific FTR. I will clean this instance up though.
| &mut files, | ||
| path.clone(), | ||
| contents.clone(), | ||
| "<generated Python recursive-model hoist>", |
There was a problem hiding this comment.
How are these synthetic sources helpful? Won't they just create confusing error messages? Seems like this would need to know the original file it was hoisted from or something.
| && tree_support_files.contains_key(&PathBuf::from("definitions.ts")); | ||
| insert_branch_index_file(&mut files, branch, has_json_runtime_module)?; | ||
| insert_files(&mut files, tree_support_files)?; | ||
| let additional_exports: &[&str] = if branch_has_json_models(branch) { |
There was a problem hiding this comment.
Explicitly checking for json models is a leak here as well.
There was a problem hiding this comment.
I don't think we were very methodical about avoiding leaks so far. You'll notice the leak is pre-existing.
There was a problem hiding this comment.
Yep. Pointing it out, if we decide not to fix it now, that's okay.
There was a problem hiding this comment.
I've been looking for them, but it is hard not to miss some. Filing an issue maybe, just as a reminder to come back and do some clean up.
Review of 18a5609 found three places where a spec had been relaxed so that a known code defect became conformant, and a set of findings that were not reached at all. The commit predates roughly half the adversarial review, so some of what it encoded had since been retracted. Reversals — the spec was amended in the wrong direction: * format.md no longer lists Java among the nanosecond-resolution targets for `time`, and no longer prescribes canonicalizing through OffsetTime/LocalTime. Java holds `time` in a String; a String carrier has no resolution of its own, so the truncation is the artificial common-denominator floor P1 forbids. Only Python's microsecond limit is a licensed loss. Also drops the stale claim at the head of the section that `time` round-trips losslessly. * allOf.md scopes the raw pre-merge gate to checks decidable on one branch that a merge could silently discard, and states that naming and satisfiability checks are not among them. The previous wording licensed running P15 value-constant checks per authored branch, which over-rejects schemas whose merged form is clean, and contradicted the delegation this file states twice elsewhere. * dependentRequired.md restores the rewrite fix-it for the draft-07 `dependencies` array form, matching the exclusiveMaximum precedent. Reader- directed porting guidance dissolved the missing-diagnostic defect instead of recording it. * generated-file-layout.md no longer asserts that reserved-name and P15 diagnostics are actionable — two findings show they are not — and instead makes applicability the obligation. * PRINCIPLES.md P1 no longer folds the binary64 exception under "provably cannot carry a wire distinction"; it is a domain restriction owned by type. Decisions the specs did not previously make, each of which blocked a well-defined code fix: * oneOf.md: a non-annotation sibling of a `oneOf` rejects, with a fix-it naming the branch. Silent discard is what P7.1 exists to prevent, and the sibling additionally suppresses two unrelated type guards. * nullability.md: constraints belong on the non-null branch, `default` on the wrapper; each rejects on the other node. Records that a wrapper default lowers against the branch's type. * PRINCIPLES.md P11: names the boundaries the failure must survive, and states that Go does not meet the clause today because the SDK's payload converter severs the error chain, inverting non-retryability. * PRINCIPLES.md 11a: defines the Violation.path grammar, including escaping for arbitrary map keys and the join for a bracket-leading segment, which the four targets currently disagree on. * min/maxProperties.md: the count is over the wire object at each boundary, never over in-memory fields; states the asymmetry the optional+nullable collapse implies rather than leaving it to be discovered. * min/maxProperties.md: the count keywords own satisfiability reconciliation against every keyword that bounds the key space, not only `required`. * ref.md: bounds the input root by the invocation root, rejects absolute refs, and defines what "reproducible" promises — module and package names must not encode the checkout's location. * PRINCIPLES.md P15: the namespace of an emitted file includes what it imports and what the emitter synthesizes into it, covering the service file's SDK imports and Go's synthesized <Service>Client. * pattern.md / maxLength.md: a materialized node's assertion measures the canonical wire form at both boundaries, and a literal is canonicalized before it is checked. Smaller corrections: const.md's Java block no longer uses the arrow switch that does not compile under --release 8; multipleOf.md stops claiming UnmarshalJSON calls the shared Validate; pattern.md's residual-risk sentence names the edges actually open; description.md reconciles "does not reflow" with the wrapping section and records that wrapping can move a doc tag into a position its tool treats as meaningful; PRINCIPLES.md P12.2 states that sharing is a requirement on the predicate rather than on the call graph, which the per-keyword rewrites in the parent commit had left it contradicting.
A whole-diff audit of 18a5609 found five further places where the amendment
made a reproduced P1 conformant or unreachable, none of them caught by the
scoped reviews, plus a set of self-contradictions the edits introduced.
Reversals:
* PRINCIPLES.md no longer says Python's last-import-wins shadowing is "not
reachable generated output". It is reachable: two service names differing
only in case fold to one origin label, the loader accepts, and the emitted
module defines the same class twice. The counterfactual framing holds only
for the cross-module model-name case the loader rejects up front.
* pattern.md gate rule 7 restores the "outside a character class" scoping. Its
absence asserted that `[]]`, `[]a]`, `[^]]` and `[]-a]` reject; they pass, and
Node in `u` mode throws on import. The hole is now stated as a hole.
* pattern.md gate rule 8 no longer claims the D7 rule keeps evaluation linear.
It fires only when the outer quantifier is unbounded, so `a{2}*` still panics
at Go package init and `^(a|b)*$` still overflows Java's stack. Both are named
as open. The neighbouring "complete gate" and "all handled" claims are
softened to match.
* maxItems.md keeps the 2^53-1 ceiling but states the obligation the ceiling
does not discharge: a bound in [2^31, 2^53-1] requires Java to emit a `long`
literal, or the package does not compile. The ceiling read as though it made
the range safe, which is the claim the finding disproves.
* deprecated.md scopes the "where the tooling supports it" hedge to TypeScript
and Python, where the limit is genuine. It was covering Go's inert
service/operation marker, which is a generator placement bug — one blank line
fixes it — not a toolchain limit.
Self-contradictions introduced by the parent commit:
* PRINCIPLES.md TS §4 said converters join a per-module namespace and, two
lines later, that they are validated run-wide. Replaced rather than appended.
* additionalProperties.md said key order is preserved and, in the same
sentence, that member order is not guaranteed. "key order" dropped from the
preserved list.
* type.md's `"null"` type-mapping row still read "only inside nullability
pattern" after the loader bullet was widened to any `oneOf` branch.
* contentEncoding.md rejected the materializing-format combination one-sidedly;
format.md had no counterpart bullet, leaving the question unadjudicated from
the other side. Mirrored.
* The inert-`$ref`-sibling carve-out was written into allOf.md and comment.md
but not ref.md, which still stated siblings merge unconditionally. Propagated,
and all three now carry a Status: unimplemented marker — the fold gate admits
only the four name keywords today, so the clause is the contract and the gate
is the defect.
Overstatements narrowed: type.md's "only a free-form oneOf object branch"
(an un-hoisted array branch lands on the same fallback); enum.md stating the
union-variant leg as fact when its own report calls it unreproduced;
format.md's ecosystem row claiming such documents are never accepted, when the
dialect gate is conditional on a declared `$schema`.
cc2a5b7 to
78e64d2
Compare
| &mut self, | ||
| path: impl Into<PathBuf>, | ||
| contents: String, | ||
| source: impl Into<String>, |
There was a problem hiding this comment.
I think I would either make source optional, or add another insert method which does not take a source, like insert_system_file or something which has a default error message.
I'm like medium on the idea though. If you don't want to do that, throw a comment on this function describing that source can be a placeholder for a generated file, and that failing should be unexpected, indicating a validation failure upstream.
Summary
Breaking changes
timeanddate-timeoffsets to the portable-18:00through+18:00range and use JavaOffsetDateTimeDEFAULT_<FIELD>exports instead of renaming an existing constant when another model is addedValidation
cargo validate(Node 26, JDK 21, .NET 9)