Software contracts describe structure, not dependency
Most software contracts are good at describing what exists.
An interface lists properties. A DTO defines the shape of transferred data. A schema describes valid fields and types. An API specification documents the operations and representations exposed by a service.
These mechanisms are valuable, but they usually leave an important question unanswered:
Which parts of the contract does each consumer actually depend on?
Suppose a system exposes the following user contract:
User
- id
- fullName
- email
- preferredLanguage
One consumer may need only id and fullName. Another may depend on email. A third may require preferredLanguage.
Structurally, all three consumers appear to use the same contract. Semantically, however, they have different dependencies and different exposure to change.
Traditional contract models frequently hide that distinction inside application code.
Availability is not dependency
A field being available does not necessarily mean that every consumer depends on it.
When a consumer receives an entire object, its visible dependency surface becomes larger than its actual semantic dependency. The contract exposes everything, while the consumer may require only a small subset.
This produces a subtle form of coupling.
If the contract changes, maintainers often evaluate compatibility globally:
Is the new version backward compatible?
Does this modification break the contract?
Must every consumer migrate?
Should the major version be incremented?
These questions treat compatibility as though it were an intrinsic property of the contract.
But compatibility is relational.
A change is compatible or incompatible with respect to a particular consumer and the specific information that consumer requires.
Adding a field should not affect a consumer that never requested it. Removing email should not affect a consumer that depends only on id. Renaming fullName matters only to consumers that use that semantic field.
The impact of change is local, even when conventional versioning makes it appear global.
Projection as the unit of consumption
Raygon is a semantic contract model designed around this observation.
Its central rule is simple:
Contracts are not consumed directly. Consumption occurs through explicit projections.
A projection declares the exact fields a consumer requires from a contract.
Conceptually, a consumer could declare:
Consumer ProfileHeader
projects User {
id
fullName
}
Another consumer could declare:
Consumer NotificationService
projects User {
id
email
preferredLanguage
}
These declarations do more than restrict access. They make intent observable.
The first consumer depends on the identity and display name of a user. The second depends on the information required to deliver localized notifications. Their coupling is no longer inferred from arbitrary property access scattered throughout the codebase.
It is part of the model.
If a field is not included in a projection, that consumer does not depend on it.
Compatibility should be local
Once consumption is projection-based, compatibility can be evaluated relative to declared dependencies.
Consider the addition of a new field named timezone. Existing projections do not include it, so their dependency surfaces remain unchanged.
Now consider the removal of email. The change is incompatible for NotificationService, but it does not affect ProfileHeader.
There is no need to declare the entire contract universally compatible or incompatible. The effect is localized to the consumers whose projections reference the changed semantic element.
This makes breakage:
bounded;
attributable;
explainable;
discoverable before unrelated consumers are forced to migrate.
A contract can evolve without pretending that every consumer observes its complete structure.
Renaming should not erase identity
Structural contract systems often model a rename as two operations:
delete the old field;
add a new field.
Semantically, that may be incorrect.
If fullName becomes displayName while preserving the same meaning, the field has not necessarily been replaced. Its identifier has changed, but its semantic identity may remain continuous.
Raygon models this through field lineage and aliases.
A rename can introduce a new canonical identifier while preserving previous identifiers as part of the field’s history. Existing projections remain traceable, and tools can identify which consumers still use an older name.
However, lineage must not be used to disguise semantic drift. If the meaning changes, the model should introduce a new semantic field rather than claiming continuity that no longer exists.
The purpose of lineage is to preserve meaning through structural evolution, not to excuse an undocumented change in meaning.
Evolution should be recorded, not erased
Raygon treats evolution as append-only semantic history.
Fields may be introduced, renamed, aliased, deprecated, or eventually removed, but their evolution should remain inspectable. Version numbers record that change occurred; they do not independently determine whether a consumer remains compatible.
This distinction matters because a higher version is not automatically incompatible, and an older version is not automatically safe.
Compatibility depends on whether the semantic fields required by a consumer remain available through their recorded identity and lineage.
Versioning describes evolution. Projections determine exposure to that evolution.
A model before a framework
Raygon consists of two related artifacts.
The first is the Raygon Contract Model, which defines the conceptual rules, invariants, projections, compatibility semantics, field lineage, and evolution model. It is designed to remain independent of any programming language or platform.
The second is an experimental TypeScript implementation that explores how these ideas can operate in executable software.
The current MVP separates the implementation into three layers:
Core, which represents contracts, fields, metadata, and graph relationships;
Runtime, which uses JavaScript Proxies to detect access to fields not declared by a consumer;
Compiler, which traverses the contract graph and provides a foundation for analysis and future tooling.
The implementation demonstrates that declared intent can be compared with actual usage. It is not yet a production-ready framework and does not currently implement the complete evolution model described by the specification.
This distinction is deliberate: the model defines the semantics, while tooling interprets them.
What Raygon is not
Raygon is not intended to replace JSON, TypeScript, OpenAPI, GraphQL, or schema validators.
It does not define a transport protocol, payload format, general-purpose programming language, or universal validation system.
Those tools answer questions such as:
What data can be represented?
Is this payload structurally valid?
Which operations does this API expose?
Which types can appear in this program?
Raygon focuses on a different set of questions:
Which semantic fields does this consumer require?
Why does that dependency exist?
Which consumers are affected by a change?
Does a renamed field preserve its identity?
Where does compatibility actually fail?
It is a model of consumption and evolution rather than a replacement for structural contracts.
Making coupling visible
Software coupling cannot be eliminated. Systems work because components depend on one another.
The real problem is coupling that remains invisible until something breaks.
By making projections explicit, Raygon treats dependency as first-class architectural information. That information can eventually support dependency graphs, change-impact analysis, deprecation diagnostics, migration assistance, semantic refactoring, and better review of contract evolution.
The goal is not merely to make contracts easier to write.
It is to make it difficult for a system to hide who depends on what, why that dependency exists, and how change propagates through it.
That leads to the question behind Raygon:
What would software evolution look like if coupling were declared before it became a failure?