Skip to content

Commit bf1c157

Browse files
authored
feat(routines): define canonical routine schemas (#72)
Add Draft 2020-12 contracts and offline validation for routine class manifests, interfaces, and specialization inputs while keeping the production catalog non-executable.
1 parent b2fc2b3 commit bf1c157

16 files changed

Lines changed: 2448 additions & 37 deletions

.github/workflows/verify.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,15 @@ jobs:
5252
working-directory: cxf-library
5353
run: python3 tools/lint/registry.py
5454

55+
- name: Install routine schema lint dependencies
56+
working-directory: cxf-library
57+
run: python3 -m pip install --requirement tools/lint/requirements-routine-schemas.txt
58+
5559
- name: routine catalog lint
5660
working-directory: cxf-library
5761
run: |
5862
python3 -m unittest discover -s tools/lint/tests -v
63+
python3 tools/lint/routine_schemas.py
5964
python3 tools/lint/routines.py
6065
6166
- name: Verify G36 source inventory

SCHEMA.md

Lines changed: 146 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ cxf-library/
2222
├── routines/ # planned control-routine catalog
2323
│ ├── registry.json # canonical class inventory
2424
│ ├── generated-registry.json # executable deployment inventory
25+
│ ├── schemas/ # canonical class/interface input schemas
2526
│ └── g36/ # G36 pins, source inventory, scope, and coverage
2627
├── tools/verify/ # Rust harness: loads each rule into the engine, runs vectors
2728
```
@@ -53,8 +54,9 @@ next free number, honoring any reservation.
5354

5455
Routine contracts are independent of fault contracts. Nothing in this section
5556
changes a fault schema identifier or fault behavior. The routine catalog is
56-
planned and non-executable. L1 adds source-tree evidence without adding a
57-
canonical class or executable deployment.
57+
schema-defined and non-executable. The current contract defines future
58+
canonical class, interface, and specialization input shapes without adding a
59+
production class, source mapping, specialization, or executable deployment.
5860

5961
Pin ownership is split by purpose:
6062

@@ -73,22 +75,148 @@ retired and MUST be absent.
7375

7476
`routines/registry.json` is the canonical class inventory. Its top-level object
7577
has exactly `schema` and `routines`; `schema` is
76-
`cxf-library/routine-registry/v2`. `routines` MUST be an array and MUST remain
77-
empty in L1. A scope anchor is not a canonical class. The source inventory
78-
defined below records Git blobs; it does not identify Modelica classes or
79-
subsequences.
78+
`cxf-library/routine-registry/v2`. `routines` MUST be an array and remains empty
79+
until production class rows are implemented. This registry remains the sole
80+
catalog inventory; the schemas below do not replace it. A scope anchor is not a
81+
canonical class. The source inventory defined below records Git blobs; it does
82+
not identify Modelica classes or subsequences.
8083

8184
`routines/generated-registry.json` is the only inventory that may eventually
8285
drive routine execution. Its top-level object has exactly `schema` and
8386
`deployments`; `schema` is
8487
`cxf-library/generated-routine-registry/v1`. `deployments` MUST be an array and
85-
MUST remain empty in L1. The verifier's `--routines` mode reads this file,
86-
accepts the empty array, and rejects nonempty arrays until the generated
87-
deployment contract is implemented.
88+
remains empty until generated deployments are implemented. The verifier's
89+
`--routines` mode reads this file, accepts the empty array, and rejects nonempty
90+
arrays until that contract is implemented.
8891

8992
Canonical IDs MUST NOT encode fixed parameter values. Generated deployment IDs
9093
and row schemas are not defined by this version.
9194

95+
### Canonical routine schema resources
96+
97+
This schema set governs four JSON Schema Draft 2020-12 resources:
98+
99+
| Path | `$id` |
100+
|---|---|
101+
| `routines/schemas/common.schema.json` | `https://open-control-library.example/schemas/routine-common-v1.json` |
102+
| `routines/schemas/class-manifest.schema.json` | `https://open-control-library.example/schemas/routine-class-manifest-v1.json` |
103+
| `routines/schemas/interface.schema.json` | `https://open-control-library.example/schemas/routine-interface-v2.json` |
104+
| `routines/schemas/specialization.schema.json` | `https://open-control-library.example/schemas/routine-specialization-v1.json` |
105+
106+
Each resource declares
107+
`https://json-schema.org/draft/2020-12/schema`. References use only the four
108+
absolute IDs above and resolve from an in-memory registry. Validation performs
109+
no network or filesystem retrieval for schema references. Objects are closed
110+
unless stated otherwise.
111+
112+
Canonical class IDs have the form
113+
`G36-05-(01..22)-<UPPERCASE-HYPHENATED-CLASS-SLUG>`. Scope IDs are invalid
114+
canonical IDs. A canonical ID identifies a parameterized engineering class; it
115+
MUST be independent of source paths and revisions, fixed parameter values,
116+
ordering, hashes, and future generated content IDs. IDs are immutable and MUST
117+
NOT be reused. Immutability and reuse are authoring and review invariants; the
118+
checker has no historical registry against which to prove them. A positive
119+
integer `revision` records contract changes separately from identity.
120+
121+
Parameter, connector, type, and dimension IDs use bounded lower-case
122+
snake_case. Stable repeated-member IDs use bounded lower-case hyphenated text
123+
beginning with a letter; a dense numeric index is not a stable member ID.
124+
Type, dimension, parameter, and connector IDs MUST be unique within their
125+
respective lists. Enum member IDs and symbols MUST be unique within their enum.
126+
127+
#### Class manifests (`cxf-library/routine-class-manifest/v1`)
128+
129+
A future class manifest has exactly `schema`, `id`, `revision`, `section`,
130+
`source`, and `artifacts`. `section` is `5.1` through `5.22`; its number MUST
131+
agree with the section encoded in `id`.
132+
133+
`source` is a closed union selected by `kind`:
134+
135+
- `upstream` records `snapshot` (`release` or `development`), an exact
136+
lower-case 40-hex Git revision, a Modelica class path, and one or more file
137+
locators. Each locator contains a safe path below
138+
`Buildings/Controls/OBC/ASHRAE/G36/` and a `sha1:<40 lowercase hex>` Git blob
139+
ID.
140+
- `independent` records one or more safe repository-relative source paths.
141+
142+
Duplicate source paths, absolute paths, backslashes, control characters, and
143+
empty, `.`, or `..` segments are invalid. `artifacts` has exactly `interface`,
144+
`specialization_schema`, and `specialization_config`. Their safe relative paths
145+
share one non-root class directory and end in `interface.json`,
146+
`specialization.schema.json`, and `specialization.json`, respectively. This is
147+
an artifact-location contract, not a production source-to-class mapping.
148+
149+
#### Interfaces (`cxf-library/routine-interface/v2`)
150+
151+
An interface has exactly `schema`, `canonical_id`, `revision`, `types`,
152+
`dimensions`, `parameters`, and `connectors`. Types and enums are local to that
153+
interface; this contract defines no global type catalog.
154+
155+
The primitive symbols are exactly `real`, `integer`, and `boolean`. String and
156+
runtime object types are excluded. A named alias selects one primitive and may
157+
record nonempty trimmed `quantity`, `unit`, and `display_unit` strings.
158+
These strings assert no QUDT, Brick, or ASHRAE 223 semantics. An enum declares
159+
a nonempty ordered list of unique stable member IDs and unique symbols. Enum
160+
values use the stable member IDs; no integer lowering code is assigned.
161+
162+
A type use is either primitive or a reference to a local named type. A shape is
163+
either scalar or an array with an ordered list of one or two dimension IDs.
164+
Dimensions have unique IDs. Their extent is either a positive fixed integer or
165+
a reference to a scalar Integer parameter. Rank greater than two, zero extents,
166+
ragged matrices, and arithmetic dimension expressions are invalid.
167+
168+
Parameters have unique IDs, a type use, shape, `fixed` or `configurable`
169+
configurability, an optional typed default, and optional numeric minimum and
170+
maximum constraints. A fixed parameter MUST have a default and cannot be
171+
assigned by specialization. A configurable parameter without a default MUST be
172+
assigned by specialization.
173+
174+
Connectors have unique IDs, `input` or `output` direction, a type use, shape,
175+
and explicit presence. Presence is `always` or `when` with a closed guard AST.
176+
Guards support `and`, `or`, `not`, and `eq`, `ne`, `lt`, `lte`, `gt`, or `gte`
177+
comparisons. Operands are scalar parameter references or typed scalar literals.
178+
Ordering comparisons require numeric operands; Integer and Real operands are
179+
compatible. Runtime signals, connectors, time, point IDs, operating states,
180+
and host or fault logic cannot appear in guards. The checker validates guard
181+
structure, references, and operand compatibility but does not evaluate a guard
182+
or resolve optional branches.
183+
184+
#### Specialization inputs (`cxf-library/routine-specialization/v1`)
185+
186+
A specialization input has exactly `schema`, `canonical_id`, `revision`,
187+
`parameters`, and `members`. `parameters` is an ordered list of unique parameter
188+
IDs and JSON values. `members` binds each parameter-driven dimension ID to a
189+
nonempty ordered list of globally unique stable member IDs.
190+
191+
The interface and specialization canonical ID and revision MUST agree with the
192+
class manifest. Specialization checks parameter existence, fixed-parameter
193+
override rejection, required configurable assignments, primitive and enum
194+
value compatibility, numeric bounds, concrete dimension extents, rectangular
195+
rank-one and rank-two values, and stable-member count. All numeric values MUST
196+
be finite. A parameter-driven dimension resolves only from a positive Integer
197+
effective value.
198+
199+
Specialization is input only. It contains no connector bindings, point IDs,
200+
resolved connector set, source map, generated CXF, runtime state, engine
201+
identity, or deployment identity.
202+
203+
#### Schema validation boundary
204+
205+
The schemas enforce required and closed shapes, discriminators, ID patterns,
206+
primitive JSON types, and array-rank bounds. `tools/lint/routine_schemas.py`
207+
adds deterministic cross-document checks for uniqueness, section coherence,
208+
reference existence and kind, finite and compatible values, dimensions,
209+
rectangular arrays, guards, and specialization completeness. It rejects
210+
duplicate JSON keys and non-finite numbers before schema validation, checks all
211+
four schema resources with `Draft202012Validator.check_schema`, and reports
212+
sorted errors without a traceback for expected failures.
213+
214+
The linter validates one coherent fixture set under
215+
`tools/lint/tests/fixtures/routine_schemas/`. Those documents are synthetic,
216+
test-only contract evidence. They MUST NOT appear below `routines/g36/` or be
217+
added to a registry, coverage claim, source inventory, book, or production
218+
catalog destination.
219+
92220
### `routines/g36/source-inventory.json` (`cxf-library/g36-source-inventory/v1`)
93221

94222
The source inventory records two independent Git-tree snapshots from
@@ -199,20 +327,21 @@ requiring placeholder directories.
199327

200328
Coverage has exactly `schema`, `profile`, `status`, `scope`, and `claims`.
201329
`schema` is `cxf-library/g36-coverage/v2`; `profile` and `status` MUST equal
202-
`scope.json`; `scope` is `scope.json`; and `claims` MUST be an empty array in
203-
L1. Coverage does not repeat scope rows or inventory and makes no completeness,
204-
implementation, or evidence claim.
330+
`scope.json`; `scope` is `scope.json`; and `claims` remains empty until
331+
production coverage claims are implemented. Coverage does not repeat scope rows
332+
or inventory and makes no completeness, implementation, or evidence claim.
205333

206-
No `routine.cxf.jsonld` may appear below `routines/g36/` in L1. The retired
334+
No `routine.cxf.jsonld` may appear below `routines/g36/` until generated
335+
deployments are implemented. The retired
207336
`routines/g36/generic/air-economizer-high-limits` fixed-variant path MUST be
208337
absent.
209338

210339
### Deferred routine contracts
211340

212-
Canonical typed routine artifact schemas, the generated deployment bundle
213-
schema, source-to-family and class mapping, semantic sidecars, specialization,
214-
and executable deployments are deferred. This version defines no routine
215-
interface, vectors, source map, or executable CXF contract.
341+
Production class manifests, interfaces, and specialization inputs remain
342+
deferred, as do source-to-family and class mapping instances, point semantics,
343+
semantic sidecars, a specializer, resolved connectors, generated deployment
344+
bundle schemas and rows, source maps, vectors, generated CXF, and execution.
216345

217346
## Design stance (why the pieces split this way)
218347

routines/README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Routine catalog
22

3-
Status: **source-inventoried and non-executable**.
3+
Status: **schema-defined and non-executable**.
44

5-
The L1 catalog separates planning, source evidence, and future routine
6-
inventories:
5+
The catalog separates planning, source evidence, schema contracts, and future
6+
routine inventories:
77

88
- `g36/scope.json` records 22 Section 5 planning anchors and their intended
99
destinations. Scope IDs are not canonical class IDs, and the destinations do
@@ -12,9 +12,11 @@ inventories:
1212
upstream G36 source root in separate release and development snapshots.
1313
`g36/LICENSE-BUILDINGS.html` retains the legal notice shared by both pins.
1414
- `registry.json` is the canonical class inventory. It remains empty until the
15-
class and subsequence inventory is defined.
15+
first production class rows are implemented.
1616
- `generated-registry.json` is the only future executable deployment inventory.
1717
It remains empty until the deployment bundle contract and specializer exist.
18+
- `schemas/` defines future class manifests, typed interfaces, and
19+
specialization inputs. It does not contain production class instances.
1820

1921
`g36/coverage.json` references the scope manifest, remains `planned`, and makes
2022
no implementation or completeness claims.
@@ -46,12 +48,26 @@ python3 tools/lint/g36_source.py --check \
4648

4749
Run the remaining catalog gates from the repository root:
4850

51+
`requirements-routine-schemas.txt` pins `jsonschema==4.26.0` and
52+
`referencing==0.37.0`.
53+
4954
```sh
50-
python3 -m unittest discover -s tools/lint/tests -v
51-
python3 tools/lint/routines.py
55+
python3 -m venv /tmp/cxf-routine-schemas
56+
/tmp/cxf-routine-schemas/bin/python -m pip install \
57+
--requirement tools/lint/requirements-routine-schemas.txt
58+
/tmp/cxf-routine-schemas/bin/python -m unittest \
59+
tools.lint.tests.test_routine_schemas -v
60+
/tmp/cxf-routine-schemas/bin/python tools/lint/routine_schemas.py
61+
/tmp/cxf-routine-schemas/bin/python -m unittest discover \
62+
-s tools/lint/tests -v
63+
/tmp/cxf-routine-schemas/bin/python tools/lint/routines.py
5264
cargo run --manifest-path tools/verify/Cargo.toml -- --routines
5365
```
5466

55-
Canonical typed artifact schemas, generated deployment bundles, semantic
56-
sidecars, source-to-family and class mapping, specialization, and executable
57-
deployments remain deferred.
67+
Canonical IDs name parameterized engineering classes, never fixed parameter
68+
variants or source locations. Local types and enums belong to one interface.
69+
The schemas cover scalar and rank-one/rank-two typed values,
70+
parameter-controlled dimensions, stable repeated-member IDs, and
71+
parameter-only optional-connector guards. They do not evaluate guards or
72+
define point semantics, connector bindings, source mapping instances,
73+
production specializations, generated deployments, or executable CXF.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://open-control-library.example/schemas/routine-class-manifest-v1.json",
4+
"title": "Open Control Library routine class manifest",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": ["schema", "id", "revision", "section", "source", "artifacts"],
8+
"properties": {
9+
"schema": {"const": "cxf-library/routine-class-manifest/v1"},
10+
"id": {
11+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/canonicalId"
12+
},
13+
"revision": {
14+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/revision"
15+
},
16+
"section": {
17+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/section"
18+
},
19+
"source": {
20+
"oneOf": [
21+
{
22+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/upstreamSource"
23+
},
24+
{
25+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/independentSource"
26+
}
27+
]
28+
},
29+
"artifacts": {
30+
"$ref": "https://open-control-library.example/schemas/routine-common-v1.json#/$defs/classArtifacts"
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)