Skip to content

Latest commit

 

History

History
17 lines (14 loc) · 10 KB

File metadata and controls

17 lines (14 loc) · 10 KB

data-modeling — notes

Hand-written. ../harvested/data-modeling.md is what the documents say; this file is what the implementation found, and it wins. Each entry names the harvested entry it answers by that entry's stable key.

Conflicts

  • Value-object base type: the design wins — Data.define is the base for every core domain type, and T::Struct is not available to this port at all. Resolves data-modeling/35fde90f. The styleguide's rule is conditional on Sorbet (T::Struct is the # typed: strict default because Sorbet cannot type Data.define members), and this SDK has no Sorbet: T::Struct is defined by sorbet-runtime, a third-party gem that would have to appear as an add_dependency in dexpace-core.gemspec, which SEAM-1/NFR-1 forbid and the gemspec audit mechanically rejects. The premise the styleguide rule rests on is therefore absent, not overruled. What the SDK does instead: every core model is Data.define with an initialize override that validates and calls super, collections duped and frozen exactly once at construction, private_class_method :new plus a validating .build, and sig/**/*.rbs carrying the per-member types RBS can express and Sorbet was wanted for. The styleguide's own reasoning for Data.define — frozen on construction, ==/hash/with for free, keyword constructor enforced — is adopted in full. The styleguide-amendment alternative (scoping the T::Struct preference to "projects using Sorbet") was considered and not taken; this is recorded as an SDK deviation instead. review · docs/work/mvp/2026-09-05-ruby-sdk-v1-roadmap-design.md · high · sha:manual-roadmap-conflict-1
  • A seam is a duck type plus a .conforms? predicate plus an RBS interface type, never a Sorbet abstract module. Resolves data-modeling/a13e9ffe. The rule is conditional on Sorbet in its mechanism — sig { abstract } stubs are sorbet-runtime, which would be an add_dependency line in dexpace-core.gemspec that SEAM-1/NFR-1 forbid and rake gates:gemspec_audit mechanically rejects — so the premise is absent rather than overruled, exactly as ../notes/type-system.md records for the wider Sorbet question. But the substitution is not simply "drop the module": the rule also says concrete implementations should include the interface, and this port deliberately does not ask that either, for a reason that is about libraries rather than about Sorbet. A seam here is implemented by a gem this project does not control and, in the common case, by an object that already has the shape — a bare lambda, a Dexpace::Pipeline, a Rack-style #call-able. docs/sdk-design-ruby/00-porting-method.md, working principle P14, makes that a principle: where the host ecosystem has converged on one API shape, the seam is defined as a structural subset of it so existing infrastructure duck-types in with zero adapter code. A nominal include would put a Dexpace:: constant into every adapter's ancestry, would make a two-line lambda non-conformant, and would buy nothing Ruby actually enforces. What the SDK does instead, per docs/sdk-design-ruby/03-seam-by-seam-idiomatic-mapping.md §3.2 and §3.4: each seam is three artifacts and no fourth — a documented duck type (#call(request, options, cancellation) for a transport; #media_type/#dump_string/#dump_bytes/#dump_to/#dump_into/#load for a codec); a .conforms? predicate on the seam's own module, which is the runtime check the registry runs before accepting a factory and which is also how SEAM-2 is satisfied, because core names the shape and never an implementation; and an RBS interface _Transport-style type in that seam's sig/ file, which is what a consumer's own steep check sees at a parameter. Assignability and runtime identity are different questions in Ruby and each artifact answers one — the same split phase 1 found for SEAM-29, where the RBS interface _Builder[T] and the module Dexpace::Builder are both needed. What is lost and is not replaced: a type checker cannot verify that an adapter implements the seam, only that it is passed where one is expected; .conforms? and dexpace-conformance's per-adapter assertions are what stand in. One residual gap is admitted rather than hidden: the synchronous and asynchronous transport seams have the same structural shape and differ only in return type, so no .conforms? can tell them apart before the first call — they are two registries and the adapter names which it registers into. The styleguide-amendment alternative (making chapter 6's interface rule read "a documented duck type, an abstract module where Sorbet is in use") was considered and not taken; this is recorded as an SDK deviation instead (phase 2). review · docs/work/mvp/phase2/2026-09-06-phase2-seam-foundations-design.md · high · sha:manual-phase2-seam-duck-types
  • The whole wire model is Ractor.shareable? as built, Request and Response included: design §4's claim stands in full, and the phase-1 design's narrowing of it (ledger row P1-9) did not survive the build. Two documents disagreed. data-modeling/996c0b12 — design §4 — says deep-freezing at construction makes the whole wire model Ractor-shareable, and the phase-1 design's P1-9 said the two models holding a URI::Generic are not. Design §4 won: this entry confirms data-modeling/996c0b12 and overrules nothing harvested. What the phase-1 design got right, verified on 2026-09-05 and again on 2026-09-15 against 3.2.11 and 4.0.6: URI::RFC3986_PARSER.parse("https://example.test/a?x=1").freeze is frozen? but not Ractor.shareable?, because URI::Generic#freeze is shallow and its @host and @path Strings stay unfrozen — and that same fact is an XCUT-15 alias rather than a shareability curiosity: URI#dup shares those Strings with the source, so the plan's input.dup.freeze left request.url.host << "x" able to change a model in place through a reader. What it got wrong: it took Ractor.make_shareable(uri) for the only route to a shareable URI and warned that the call deep-freezes URI::RFC3986_PARSER, a process-global object, in place — the in-place hazard data-modeling/fa3eaf6e and data-modeling/59efaae6 name, one level further out than they state it. That warning is moot: URI::RFC3986_PARSER.frozen? and Ractor.shareable?(URI::RFC3986_PARSER) are already true on both interpreters, because the uri gem freezes the parser at definition, and the SDK never calls make_shareable on a URI in any case. What the SDK does: Dexpace::URL.parse! re-parses a URI input from its text (never dup, which aliases) and freezes each String-valued instance variable of the URI it now owns, skipping the parser reference; Model.own keeps every collection member a deep-frozen copy; Model.frozen_string copies and freezes every caller-supplied String member, Response#reason included; and so every model — Request and Response given a nil or frozen body, which is opaque in phase 1 and carried as given — is Ractor.shareable? with no global touched. request_test.rb and response_test.rb assert it, so a later change to URL.parse! cannot lose the property silently. P1-9 is retired as built and P1-13 records the ownership rule (docs/work/mvp/phase1/2026-09-05-phase1-core-http-domain-model-design.md). Ractor is still load-bearing nowhere (docs/sdk-design-ruby/09-toolchain-and-quality-gates.md's runtime floor keeps it out of the supported surface). This entry stands where the phase-1 design's planning-time entry stood under ## Superseded (sha:manual-phase1-ractor-uri), which claimed the narrowing and was withdrawn once the build disproved it; the phase 3 to 8 design documents that cite that entry's former key as "narrowing the shareability claim" describe the planning-time reading, and each phase's start-of-phase note query surfaces this entry instead. review · docs/work/mvp/phase1/2026-09-05-phase1-core-http-domain-model-checklist.md · high · sha:manual-phase1-ractor-uri-as-built

Superseded

  • Data#with does not call an initialize override on Ruby 3.2, so it does not validate there; it does on 3.4 and 4.0. Supersedes data-modeling/c4fe4732, whose "#with produces a copy with changes" was verified on 3.4.10 only. Re-verified on 2026-09-05 against three installed interpreters with one Data subclass whose initialize raises on a nil member: on 3.2.11 instance.with(member: nil) returns #<data … member=nil> and the override is never invoked; on 3.4.10 and 4.0.6 it raises. Why it matters rather than being a curiosity: docs/sdk-design-ruby/04-domain-model-construction.md makes #with the only derivation path for every value type with no builder — MediaType, Status, Protocol, Method, HeaderName and the conditional-request helpers — so following the harvested wording ships unvalidated derivation on the declared floor (required_ruby_version >= 3.2) while every HTTP-4/SEAM-29 check passes on the developer's Ruby. That is the same shape as the URI::DEFAULT_PARSER split design §3.5 pins against: it passes where you look and fails where you do not. What the SDK does instead: Dexpace::Model, the module every core Data type includes, overrides #with to route through the type's own validating .build — changes.empty? ? self : self.class.build(**to_h, **changes) — so derivation re-validates identically on 3.2, 3.3, 3.4 and 4.0, and a value type added by any later phase inherits it by including the module rather than by remembering this entry. The CI matrix runs the real suite on every row, which is what keeps the guarantee standing; locally the check is mise exec ruby@3.2.11 -- bundle exec rake test:gems, and deleting the override turns that run red while the 4.0 run stays green. Two neighbouring facts in the same entry are unaffected and still hold on all three interpreters: a Data instance is frozen on construction, and ==/eql?/hash are generated over all members. review · docs/work/mvp/phase1/2026-09-05-phase1-core-http-domain-model-design.md · high · sha:manual-phase1-data-with