okonomi / opm

Type contracts

A .opm says what one vendor builds. A type contract says what a kind of product owes, across every vendor that builds one.

That is the whole of its job. A host placing a window needs to ask for a width without knowing whose window it is; a host that has drawn a wall run needs to hand over the route without knowing whose skirting it is. The contract is where those demands are written down once, in a document the platform owns rather than in a convention each client rediscovers.


The two namespaces

Every contract id is <namespace>/<type>@<version>core/opening@1, okonomi/window@1.

core/ is the shared layer. A document under it describes a kind of product and is claimed by no generator: something that fills a hole somebody else cut, something swept along a route, or the root everything stands on.

Everything else is somebody's. A document under a vendor's namespace is an article that is actually sold, and narrows one of the above.

The split exists so a second vendor can name a window without arguing with the first about what the word means. okonomi/window@1 and acme/window@1 are two articles keeping one agreement, core/opening@1. Without the namespace, the first collision would have to be settled by whoever committed second.

core/base@1 extends nothing and states only the frame and the units. A vendor building a family nobody has described yet extends that and says the rest.


What a contract says

{
  "namespace": "core",
  "type": "opening",
  "version": 1,
  "extends": "core/base@1",
  "label": "Opening",
  "description": "…",
  "requires": [ … ],
  "forbids":  [ … ],
  "frame":    { … },
  "slots":    { "recommends": [ … ] },
  "derives":  { "keys": [ … ] }
}

requires — what every claimant must declare

An entry names a key, its dataType, optionally a unit, and a why. A generator that does not declare the key, declares it as the wrong type, or declares it in the wrong unit does not satisfy the contract.

"At least", never "exactly". okonomi/window@1 is claimed by a casement that declares hand and by a fixed light that does not. A check demanding an exact key set would refuse half of any real catalogue.

An entry may also carry options — and these are named, never enforced. A type can say "every combination has something in place three, and it is one of a small set of articles"; it cannot say which set, because that is the range. One vendor sells a rotary dimmer, the next a push dimmer, a third neither, and a contract that pinned the list would make every range but the first non-compliant for stocking what it actually stocks. What the list is for is a host rendering a choice before it has asked anybody. The generator's own options always win.

forbids — what a claimant may not declare

The mirror, and the sharper half. A key is forbidden when the product answers it rather than asking it: a switch combination forbids width because its length falls out of the 71 mm module pitch, and a run forbids length because its length is the path it was given. A caller stating either would be stating something the product decides, and the two would disagree the moment a gang was added or a corner moved.

The two openings part company here and it is worth seeing: a window forbids depth because the frame's depth is the product's, and a door requires it because the lining spans the wall.

frame — where the geometry sits

units, axes, and a datum saying where y = 0 is: bottom (nothing hangs below the origin) or centre (min === -max). It is merged key by key down the chain, so a type states only what differs from its parent.

core/base@1 deliberately states no datum. Where y = 0 sits is exactly what the families disagree about — a window's own underside, a door's finished floor, a combination's centre — so it is the first thing a child says for itself.

derives — who binds the value

The one thing a host cannot work out for itself and a vendor has no business deciding. A width comes off the hole in the wall; a handing comes off which jamb the sash hangs on; a wall run is the drawing. Everything else is chosen — by a provider setting up an article, or by whoever is placing it.

It names, it does not demand. A Bauart with no hand is not asked for one; this says what the scene answers when the input is there.

And it is about binding, not about measuring. The obvious reading is "what the plan cut the hole for", and that survives exactly as long as every family is an opening. It broke twice: nothing about a swing direction is measured off a wall, and nothing cuts a hole for a faceplate — yet the scene decides both. So the question this list answers is who fills the value in, per request or once on a template.

scope is a different question, and the two disagree on purpose. Scope says who demanded the field of a vendor; derives says who fills it in.

slots.recommends — what the family is usually made of

A recommendation, not a demand. A missing recommended slot is not a violation.


Inheritance

extends folds a parent in:

A cycle is an error, and a contract that extends itself is refused by name.

A published contract is served resolved. A client asking what okonomi/window@1 demands gets the width and height it inherits from core/opening@1 in the same answer, not a second fetch and a merge of its own. The registry also serves each document as written, at /contracts/<id>/source, because somebody writing a new contract needs to see what its parent states itself.


Writing one

  1. Pick the namespace. Yours, unless you are describing a kind of product rather than an article — and if you are, propose it for core/.
  2. Extend something. core/opening@1 if the thing fills a hole somebody else cut. core/path_run@1 if it follows a route. core/base@1 if neither, and then say so by forbidding the keys that do not apply rather than by leaving them out. A forbid with a why is a document; an omission is a silence.
  3. Name the datum. Every contract but the root states one.
  4. Demand what a host must be able to state, and no more. Every key in requires is a key every vendor must implement, for ever.
  5. Say why, in every entry. The why is not decoration: it is what somebody reads when their generator is refused, and it is the only place the reasoning survives.