Skip to content

Commit ec1324b

Browse files
authored
feat(routines): add fixed-dimension member identity (#77)
Bump routine interfaces to v3 so fixed dimensions own ordered authored member IDs, while parameter-driven dimensions retain specialization ownership. Enforce extent counts and cross-dimension uniqueness with deterministic schema regressions.
1 parent 2a3e9e1 commit ec1324b

6 files changed

Lines changed: 249 additions & 32 deletions

File tree

SCHEMA.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ This schema set governs six JSON Schema Draft 2020-12 resources:
102102
|---|---|
103103
| `routines/schemas/common.schema.json` | `https://open-control-library.example/schemas/routine-common-v1.json` |
104104
| `routines/schemas/class-manifest.schema.json` | `https://open-control-library.example/schemas/routine-class-manifest-v1.json` |
105-
| `routines/schemas/interface.schema.json` | `https://open-control-library.example/schemas/routine-interface-v2.json` |
105+
| `routines/schemas/interface.schema.json` | `https://open-control-library.example/schemas/routine-interface-v3.json` |
106106
| `routines/schemas/specialization.schema.json` | `https://open-control-library.example/schemas/routine-specialization-v1.json` |
107107
| `routines/schemas/routine-semantic-profile.schema.json` | `https://open-control-library.example/schemas/routine-semantic-profile-v1.json` |
108108
| `routines/schemas/routine-derivation-manifest.schema.json` | `https://open-control-library.example/schemas/routine-derivation-manifest-v1.json` |
@@ -112,8 +112,8 @@ Each resource declares
112112
fragments or the six absolute IDs above and resolve from an in-memory registry.
113113
Validation performs no network or filesystem retrieval for schema references.
114114
Objects are closed unless stated otherwise. Semantic-only definitions belong to
115-
the semantic-profile resource; `routine-common-v1` remains the existing routine
116-
class/interface contract.
115+
the semantic-profile resource. `routine-common-v1` retains its unchanged reusable
116+
definitions; the interface v3 resource defines its revised dimension form locally.
117117

118118
Canonical class IDs have the form
119119
`G36-05-(01..22)-<UPPERCASE-HYPHENATED-CLASS-SLUG>`. Scope IDs are invalid
@@ -152,7 +152,7 @@ share one non-root class directory and end in `interface.json`,
152152
`specialization.schema.json`, and `specialization.json`, respectively. This is
153153
an artifact-location contract, not a production source-to-class mapping.
154154

155-
#### Interfaces (`cxf-library/routine-interface/v2`)
155+
#### Interfaces (`cxf-library/routine-interface/v3`)
156156

157157
An interface has exactly `schema`, `canonical_id`, `revision`, `types`,
158158
`dimensions`, `parameters`, and `connectors`. Types and enums are local to that
@@ -167,9 +167,15 @@ values use the stable member IDs; no integer lowering code is assigned.
167167

168168
A type use is either primitive or a reference to a local named type. A shape is
169169
either scalar or an array with an ordered list of one or two dimension IDs.
170-
Dimensions have unique IDs. Their extent is either a positive fixed integer or
171-
a reference to a scalar Integer parameter. Rank greater than two, zero extents,
172-
ragged matrices, and arithmetic dimension expressions are invalid.
170+
Dimensions have unique IDs. A fixed dimension declares a positive integer
171+
extent and a nonempty ordered `members` list whose count MUST equal that extent.
172+
A parameter-driven dimension references a scalar Integer parameter and MUST NOT
173+
declare canonical members; `routine-specialization/v1` owns its member list.
174+
Dimension members are authored stable identities, not identities derived from
175+
array ordinals. Authored list order is contract order. Within one interface and
176+
specialization pair, member IDs MUST be unique across every fixed and
177+
parameter-driven dimension. Rank greater than two, zero extents, ragged
178+
matrices, and arithmetic dimension expressions are invalid.
173179

174180
Parameters have unique IDs, a type use, shape, `fixed` or `configurable`
175181
configurability, an optional typed default, and optional numeric minimum and
@@ -192,15 +198,17 @@ or resolve optional branches.
192198
A specialization input has exactly `schema`, `canonical_id`, `revision`,
193199
`parameters`, and `members`. `parameters` is an ordered list of unique parameter
194200
IDs and JSON values. `members` binds each parameter-driven dimension ID to a
195-
nonempty ordered list of globally unique stable member IDs.
201+
nonempty ordered list of stable member IDs. Fixed dimensions are invalid targets.
202+
These IDs MUST be unique across the specialization's member records and against
203+
the canonical members of every fixed dimension in the paired interface.
196204

197205
The interface and specialization canonical ID and revision MUST agree with the
198206
class manifest. Specialization checks parameter existence, fixed-parameter
199207
override rejection, required configurable assignments, primitive and enum
200208
value compatibility, numeric bounds, concrete dimension extents, rectangular
201-
rank-one and rank-two values, and stable-member count. All numeric values MUST
202-
be finite. A parameter-driven dimension resolves only from a positive Integer
203-
effective value.
209+
rank-one and rank-two values, and parameter-driven member count. All numeric
210+
values MUST be finite. A parameter-driven dimension resolves only from a
211+
positive Integer effective value, and its member count MUST equal that value.
204212

205213
Specialization is input only. It contains no connector bindings, point IDs,
206214
resolved connector set, source map, generated CXF, runtime state, engine

routines/README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,15 @@ cargo run --manifest-path tools/verify/Cargo.toml -- --routines
7676
Canonical IDs name parameterized engineering classes, never fixed parameter
7777
variants or source locations. Local types and enums belong to one interface.
7878
The schemas cover scalar and rank-one/rank-two typed values,
79-
parameter-controlled dimensions, stable repeated-member IDs, and
80-
parameter-only optional-connector guards. They do not evaluate guards or
81-
define production connector bindings, source mapping instances,
82-
specializations, generated deployments, or executable CXF.
79+
fixed and parameter-controlled dimensions, stable repeated-member IDs, and
80+
parameter-only optional-connector guards. A fixed dimension owns an ordered
81+
canonical member list in interface v3 whose count equals its extent. A
82+
parameter-driven dimension has no canonical member list; specialization v1 owns
83+
its ordered members. Member IDs are authored stable identities rather than
84+
array ordinals and are unique across all dimensions in an interface and
85+
specialization pair. The schemas do not evaluate guards or define production
86+
connector bindings, source mapping instances, specializations, generated
87+
deployments, or executable CXF.
8388

8489
The semantic and derivation schemas are exercised only by synthetic fixtures
8590
under `tools/lint/tests/fixtures/routine_semantics/`. Validation is local and

routines/schemas/interface.schema.json

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,65 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "https://open-control-library.example/schemas/routine-interface-v2.json",
3+
"$id": "https://open-control-library.example/schemas/routine-interface-v3.json",
44
"title": "Open Control Library canonical routine interface",
5+
"$defs": {
6+
"dimension": {
7+
"oneOf": [
8+
{
9+
"type": "object",
10+
"additionalProperties": false,
11+
"required": ["id", "extent", "members"],
12+
"properties": {
13+
"id": {
14+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/localId"
15+
},
16+
"extent": {
17+
"type": "object",
18+
"additionalProperties": false,
19+
"required": ["kind", "value"],
20+
"properties": {
21+
"kind": {"const": "fixed"},
22+
"value": {"type": "integer", "minimum": 1}
23+
}
24+
},
25+
"members": {
26+
"type": "array",
27+
"minItems": 1,
28+
"items": {
29+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/stableMemberId"
30+
}
31+
}
32+
}
33+
},
34+
{
35+
"type": "object",
36+
"additionalProperties": false,
37+
"required": ["id", "extent"],
38+
"properties": {
39+
"id": {
40+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/localId"
41+
},
42+
"extent": {
43+
"type": "object",
44+
"additionalProperties": false,
45+
"required": ["kind", "parameter"],
46+
"properties": {
47+
"kind": {"const": "parameter"},
48+
"parameter": {
49+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/localId"
50+
}
51+
}
52+
}
53+
}
54+
}
55+
]
56+
}
57+
},
558
"type": "object",
659
"additionalProperties": false,
760
"required": ["schema", "canonical_id", "revision", "types", "dimensions", "parameters", "connectors"],
861
"properties": {
9-
"schema": {"const": "cxf-library/routine-interface/v2"},
62+
"schema": {"const": "cxf-library/routine-interface/v3"},
1063
"canonical_id": {
1164
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/canonicalId"
1265
},
@@ -22,7 +75,7 @@
2275
"dimensions": {
2376
"type": "array",
2477
"items": {
25-
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/dimension"
78+
"$ref": "#/$defs/dimension"
2679
}
2780
},
2881
"parameters": {

tools/lint/routine_schemas.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
CLASS_MANIFEST_ID = (
2626
"https://open-control-library.example/schemas/routine-class-manifest-v1.json"
2727
)
28-
INTERFACE_ID = "https://open-control-library.example/schemas/routine-interface-v2.json"
28+
INTERFACE_ID = "https://open-control-library.example/schemas/routine-interface-v3.json"
2929
SPECIALIZATION_ID = (
3030
"https://open-control-library.example/schemas/routine-specialization-v1.json"
3131
)
@@ -650,9 +650,24 @@ def _check_interface_and_specialization(interface, specialization, errors):
650650
f"{interface_label}: $.parameters[{index}].constraints minimum exceeds maximum"
651651
)
652652

653+
all_member_locations = {}
653654
for index, dimension in enumerate(interface["dimensions"]):
654655
extent = dimension["extent"]
655-
if extent["kind"] != "parameter":
656+
if extent["kind"] == "fixed":
657+
members = dimension["members"]
658+
if len(members) != extent["value"]:
659+
errors.append(
660+
f"{interface_label}: $.dimensions[{index}].members: expected {extent['value']} members, found {len(members)}"
661+
)
662+
for member_index, member_id in enumerate(members):
663+
location = f"{interface_label}: $.dimensions[{index}].members[{member_index}]"
664+
first_location = all_member_locations.get(member_id)
665+
if first_location is not None:
666+
errors.append(
667+
f"{location}: duplicate stable member {member_id!r}; first used at {first_location}"
668+
)
669+
else:
670+
all_member_locations[member_id] = location
656671
continue
657672
parameter_id = extent["parameter"]
658673
parameter = parameters.get(parameter_id)
@@ -789,7 +804,6 @@ def _check_interface_and_specialization(interface, specialization, errors):
789804
errors.append(
790805
f"{specialization_label}: parameter-driven dimension {missing!r} requires stable members"
791806
)
792-
all_member_ids = {}
793807
for index, record in enumerate(specialization["members"]):
794808
dimension_id = record["dimension"]
795809
dimension = dimensions.get(dimension_id)
@@ -807,12 +821,14 @@ def _check_interface_and_specialization(interface, specialization, errors):
807821
f"{specialization_label}: $.members[{index}].members: expected {expected} members, found {len(record['members'])}"
808822
)
809823
for member_index, member_id in enumerate(record["members"]):
810-
if member_id in all_member_ids:
824+
location = f"{specialization_label}: $.members[{index}].members[{member_index}]"
825+
first_location = all_member_locations.get(member_id)
826+
if first_location is not None:
811827
errors.append(
812-
f"{specialization_label}: $.members[{index}].members[{member_index}]: duplicate stable member {member_id!r}"
828+
f"{location}: duplicate stable member {member_id!r}; first used at {first_location}"
813829
)
814830
else:
815-
all_member_ids[member_id] = (index, member_index)
831+
all_member_locations[member_id] = location
816832

817833

818834
def _check_cross_document(manifest, interface, specialization, errors):

tools/lint/tests/fixtures/routine_schemas/interface.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"schema": "cxf-library/routine-interface/v2",
2+
"schema": "cxf-library/routine-interface/v3",
33
"canonical_id": "G36-05-16-SYNTHETIC-SCHEMA-TEST",
44
"revision": 1,
55
"types": [
@@ -31,7 +31,8 @@
3131
"dimensions": [
3232
{
3333
"id": "fixed_pair",
34-
"extent": {"kind": "fixed", "value": 2}
34+
"extent": {"kind": "fixed", "value": 2},
35+
"members": ["primary", "secondary"]
3536
},
3637
{
3738
"id": "zones",

0 commit comments

Comments
 (0)