@primmel/primmel 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9169,6 +9169,227 @@ function parseInstances(block) {
9169
9169
  }
9170
9170
  return out;
9171
9171
  }
9172
+ function parseProgramArgs(block) {
9173
+ const out = {};
9174
+ const t = tokenize(block);
9175
+ let i = 0;
9176
+ while (i < t.length) {
9177
+ const key = stripColon(t[i++]);
9178
+ if (i >= t.length) {
9179
+ break;
9180
+ }
9181
+ if (t[i] === ":") {
9182
+ i++;
9183
+ }
9184
+ if (i < t.length) {
9185
+ const read = readValueToken(t, i);
9186
+ out[key] = stripWrapping(read.text);
9187
+ i = read.next;
9188
+ }
9189
+ }
9190
+ return out;
9191
+ }
9192
+ function parseVerify(block) {
9193
+ const v = { read: "", tolerance: "" };
9194
+ const t = tokenize(block);
9195
+ let i = 0;
9196
+ while (i < t.length) {
9197
+ const cmd = t[i++];
9198
+ if (i >= t.length) {
9199
+ break;
9200
+ }
9201
+ if (cmd === "read") {
9202
+ v.read = stripWrapping(t[i++]);
9203
+ } else if (cmd === "tolerance") {
9204
+ v.tolerance = stripWrapping(t[i++]);
9205
+ } else {
9206
+ unwrapBlock(t[i++]);
9207
+ }
9208
+ }
9209
+ return v;
9210
+ }
9211
+ function parseProgramBody(block, entryKeyword, makeEntry, parseEntryFacets, errCtx) {
9212
+ const program = {
9213
+ description: "",
9214
+ entries: [],
9215
+ sourceRefs: []
9216
+ };
9217
+ const t = tokenize(block);
9218
+ let i = 0;
9219
+ while (i < t.length) {
9220
+ const command = t[i++];
9221
+ if (i >= t.length) {
9222
+ throw new Error(
9223
+ `Parsing error: ${errCtx}: Expecting value for ${command}`
9224
+ );
9225
+ }
9226
+ if (command === "description") {
9227
+ program.description = stripWrapping(t[i++]);
9228
+ } else if (command === "source") {
9229
+ program.sourceRefs.push(readSource(unwrapBlock(t[i++])));
9230
+ } else if (command === "ref") {
9231
+ const rr = parseRef(t, i, stripWrapping, unwrapBlock);
9232
+ foldRefIntoLegacy(program, rr.ref);
9233
+ i = rr.next;
9234
+ } else if (command === entryKeyword) {
9235
+ const entry = makeEntry();
9236
+ let value = t[i++];
9237
+ if (!value.startsWith("{")) {
9238
+ entry.order = Number(stripWrapping(value));
9239
+ value = i < t.length && t[i].startsWith("{") ? t[i++] : "";
9240
+ }
9241
+ if (value.startsWith("{")) {
9242
+ parseEntryFacets(unwrapBlock(value), entry);
9243
+ }
9244
+ program.entries.push(entry);
9245
+ } else {
9246
+ i = skipUnknownValue(t, i, command);
9247
+ }
9248
+ }
9249
+ return program;
9250
+ }
9251
+ function parsePreparation(block, id) {
9252
+ return parseProgramBody(
9253
+ block,
9254
+ "step",
9255
+ () => ({
9256
+ order: null,
9257
+ action: "",
9258
+ drive: "",
9259
+ args: {},
9260
+ hold: "",
9261
+ verify: null
9262
+ }),
9263
+ (stepBlock, step) => {
9264
+ forEachEntry(
9265
+ stepBlock,
9266
+ (facet, value) => {
9267
+ if (facet === "action") {
9268
+ step.action = unwrapped(value);
9269
+ } else if (facet === "drive") {
9270
+ step.drive = stripWrapping(value());
9271
+ } else if (facet === "args") {
9272
+ step.args = parseProgramArgs(unwrapBlock(value()));
9273
+ } else if (facet === "hold") {
9274
+ step.hold = stripWrapping(value());
9275
+ } else if (facet === "verify") {
9276
+ step.verify = parseVerify(unwrapBlock(value()));
9277
+ } else {
9278
+ return false;
9279
+ }
9280
+ return true;
9281
+ },
9282
+ { construct: "conformance_test.preparation", id }
9283
+ );
9284
+ },
9285
+ `conformance_test.preparation. ID ${id}`
9286
+ );
9287
+ }
9288
+ function parseStimulus(block, id) {
9289
+ return parseProgramBody(
9290
+ block,
9291
+ "point",
9292
+ () => ({
9293
+ order: null,
9294
+ drive: "",
9295
+ args: {},
9296
+ hold: "",
9297
+ freshWithin: "",
9298
+ acceptance: ""
9299
+ }),
9300
+ (pointBlock, point) => {
9301
+ forEachEntry(
9302
+ pointBlock,
9303
+ (facet, value) => {
9304
+ if (facet === "drive") {
9305
+ point.drive = stripWrapping(value());
9306
+ } else if (facet === "args") {
9307
+ point.args = parseProgramArgs(unwrapBlock(value()));
9308
+ } else if (facet === "hold") {
9309
+ point.hold = stripWrapping(value());
9310
+ } else if (facet === "fresh_within") {
9311
+ point.freshWithin = stripWrapping(value());
9312
+ } else if (facet === "acceptance") {
9313
+ point.acceptance = stripWrapping(value());
9314
+ } else {
9315
+ return false;
9316
+ }
9317
+ return true;
9318
+ },
9319
+ { construct: "conformance_test.stimulus", id }
9320
+ );
9321
+ },
9322
+ `conformance_test.stimulus. ID ${id}`
9323
+ );
9324
+ }
9325
+ function dumpProgramArgs(args2) {
9326
+ const keys = Object.keys(args2);
9327
+ if (keys.length === 0) {
9328
+ return "";
9329
+ }
9330
+ return " args { " + keys.map((k) => k + ': "' + escapeString(args2[k]) + '"').join(" ") + " }";
9331
+ }
9332
+ function dumpPreparation(program) {
9333
+ let out = " preparation {\n";
9334
+ if (program.description !== "") {
9335
+ out += ' description "' + escapeString(program.description) + '"\n';
9336
+ }
9337
+ for (const step of program.entries) {
9338
+ out += " step" + (step.order !== null ? " " + step.order : "") + " {";
9339
+ if (step.action !== "") {
9340
+ out += ' action "' + escapeString(step.action) + '"';
9341
+ }
9342
+ if (step.drive !== "") {
9343
+ out += " drive " + dumpBareSafe(step.drive);
9344
+ }
9345
+ out += dumpProgramArgs(step.args);
9346
+ if (step.hold !== "") {
9347
+ out += " hold " + dumpBareSafe(step.hold);
9348
+ }
9349
+ if (step.verify) {
9350
+ out += " verify {";
9351
+ if (step.verify.read !== "") {
9352
+ out += " read " + dumpBareSafe(step.verify.read);
9353
+ }
9354
+ if (step.verify.tolerance !== "") {
9355
+ out += " tolerance " + dumpBareSafe(step.verify.tolerance);
9356
+ }
9357
+ out += " }";
9358
+ }
9359
+ out += " }\n";
9360
+ }
9361
+ for (const src of program.sourceRefs) {
9362
+ out += dumpSourceRefAsRef(src, " ", escapeString);
9363
+ }
9364
+ return out + " }\n";
9365
+ }
9366
+ function dumpStimulus(program) {
9367
+ let out = " stimulus {\n";
9368
+ if (program.description !== "") {
9369
+ out += ' description "' + escapeString(program.description) + '"\n';
9370
+ }
9371
+ for (const point of program.entries) {
9372
+ out += " point" + (point.order !== null ? " " + point.order : "") + " {";
9373
+ if (point.drive !== "") {
9374
+ out += " drive " + dumpBareSafe(point.drive);
9375
+ }
9376
+ out += dumpProgramArgs(point.args);
9377
+ if (point.hold !== "") {
9378
+ out += " hold " + dumpBareSafe(point.hold);
9379
+ }
9380
+ if (point.freshWithin !== "") {
9381
+ out += " fresh_within " + dumpBareSafe(point.freshWithin);
9382
+ }
9383
+ if (point.acceptance !== "") {
9384
+ out += " acceptance " + dumpBareSafe(point.acceptance);
9385
+ }
9386
+ out += " }\n";
9387
+ }
9388
+ for (const src of program.sourceRefs) {
9389
+ out += dumpSourceRefAsRef(src, " ", escapeString);
9390
+ }
9391
+ return out + " }\n";
9392
+ }
9172
9393
  var parseConformanceTest = function(id, data) {
9173
9394
  const result = {
9174
9395
  id,
@@ -9180,6 +9401,8 @@ var parseConformanceTest = function(id, data) {
9180
9401
  bindsTo: [],
9181
9402
  applicability: [],
9182
9403
  procedure: [],
9404
+ preparation: null,
9405
+ stimulus: null,
9183
9406
  measurements: [],
9184
9407
  kind: "",
9185
9408
  obligation: "",
@@ -9302,6 +9525,10 @@ var parseConformanceTest = function(id, data) {
9302
9525
  }
9303
9526
  } else if (keyword === "procedure_steps") {
9304
9527
  result.procedureSteps = readStringList(value());
9528
+ } else if (keyword === "preparation") {
9529
+ result.preparation = parsePreparation(unwrapBlock(value()), id);
9530
+ } else if (keyword === "stimulus") {
9531
+ result.stimulus = parseStimulus(unwrapBlock(value()), id);
9305
9532
  } else if (keyword === "kind") {
9306
9533
  result.kind = stripWrapping(value());
9307
9534
  } else if (keyword === "obligation") {
@@ -9432,6 +9659,12 @@ var dumpConformanceTest = function(ct) {
9432
9659
  if (ct.procedureSteps && ct.procedureSteps.length > 0) {
9433
9660
  out += " procedure_steps { " + ct.procedureSteps.join(" ") + " }\n";
9434
9661
  }
9662
+ if (ct.preparation) {
9663
+ out += dumpPreparation(ct.preparation);
9664
+ }
9665
+ if (ct.stimulus) {
9666
+ out += dumpStimulus(ct.stimulus);
9667
+ }
9435
9668
  if (ct.measurements.length > 0) {
9436
9669
  out += " validate_measurement {\n";
9437
9670
  for (const m of ct.measurements) {
@@ -11574,6 +11807,13 @@ var parsePackage = function(data) {
11574
11807
  i = cc.next;
11575
11808
  } else if (cmd === "scheme_type" || cmd === "schemeType") {
11576
11809
  manifest.schemeType = stripWrapping(t[i++]);
11810
+ } else if (cmd === "license_key" || cmd === "licenseKey") {
11811
+ if (manifest.licenseKey !== void 0) {
11812
+ (manifest.licenseKeyDuplicates ??= []).push(manifest.licenseKey);
11813
+ }
11814
+ manifest.licenseKey = stripWrapping(t[i++]);
11815
+ } else if (cmd === "license_holder" || cmd === "licenseHolder") {
11816
+ manifest.licenseHolder = stripWrapping(t[i++]);
11577
11817
  } else if (cmd === "provides") {
11578
11818
  manifest.provides = readList(t[i++]);
11579
11819
  } else if (cmd === "requires") {
@@ -22655,6 +22895,40 @@ var CHECK_RULES = [
22655
22895
  "normal",
22656
22896
  "TODO.roadmap/36, doctrine ch. 15 \xA715.3/\xA715.9"
22657
22897
  ),
22898
+ // ── the license facets (smart TODO.external-refs/04; the entitlement
22899
+ // facet + catalog) ── The manifest declares the package's licensed
22900
+ // nature: one `license_key` (the entitlement catalog key the
22901
+ // platform's license gate reads) and its `license_holder` (the
22902
+ // copyright owner). A package without a key is public content — the
22903
+ // OIML models stay license-free — so every leg keys on the key's
22904
+ // presence. C144: the catalog-key shape. C145: at most one
22905
+ // license_key per package (the parser records the overwritten earlier
22906
+ // declarations). C146: the holder required with the key (a bare
22907
+ // holder stays legal — attribution without an entitlement claim).
22908
+ R(
22909
+ "C144",
22910
+ "license-key-shape",
22911
+ "supply-chain",
22912
+ "error",
22913
+ "normal",
22914
+ "smart TODO.external-refs/04 (the entitlement facet + catalog)"
22915
+ ),
22916
+ R(
22917
+ "C145",
22918
+ "license-key-unique",
22919
+ "supply-chain",
22920
+ "error",
22921
+ "normal",
22922
+ "smart TODO.external-refs/04 (the entitlement facet + catalog)"
22923
+ ),
22924
+ R(
22925
+ "C146",
22926
+ "license-holder-required",
22927
+ "supply-chain",
22928
+ "error",
22929
+ "normal",
22930
+ "smart TODO.external-refs/04 (the entitlement facet + catalog)"
22931
+ ),
22658
22932
  // ── subject-intrinsic constraints (TODO.roadmap/51, BUG.R60-SSOT gap 7)
22659
22933
  // The constraint construct's declaration shape — the kernel mirror of
22660
22934
  // the OIML SMART constraints.yaml schema (stereotype «inv», one ocl{…}
@@ -22804,6 +23078,37 @@ var CHECK_RULES = [
22804
23078
  "normal",
22805
23079
  "smart architecture-gaps-2026-07.md E10, data/schemas/test-sequences.yaml"
22806
23080
  ),
23081
+ // ── the executable test programs (smart TODO.twin-demo/03 — the
23082
+ // model-drive bindings) ──
23083
+ // The conformance test's preparation/stimulus facets are the
23084
+ // machine-executable half of the test declaration. C147 is the
23085
+ // program shape: entry orders are positive integers unique in the
23086
+ // program; a preparation step carries action AND/OR drive; a verify
23087
+ // block carries read AND tolerance; a stimulus point carries a drive
23088
+ // and an acceptance reference. C148 is the duration discipline: hold
23089
+ // and fresh_within parse as duration windows (the serve contract's
23090
+ // vocabulary), and a stimulus point declares fresh_within (§14.12's
23091
+ // no-stale-semantics doctrine). Reference RESOLUTION (the drive
23092
+ // operation names against the twin kind's declared vocabulary, the
23093
+ // acceptance/tolerance refs against requirement nodes) is the
23094
+ // consumer-side conformance leg's job — the kernel checks
23095
+ // syntax/shape only (the C92/C93 vs R39 split).
23096
+ R(
23097
+ "C147",
23098
+ "test-program-shape",
23099
+ "base",
23100
+ "error",
23101
+ "normal",
23102
+ "smart TODO.twin-demo/03 (the model-drive bindings)"
23103
+ ),
23104
+ R(
23105
+ "C148",
23106
+ "test-program-duration",
23107
+ "base",
23108
+ "error",
23109
+ "normal",
23110
+ "smart TODO.twin-demo/03 (the model-drive bindings)"
23111
+ ),
22807
23112
  // ── the per-test evaluation-formula traces (smart gap-close E11,
22808
23113
  // analysis/architecture-gaps-2026-07.md; the smart contract
22809
23114
  // data/schemas/formulas-used.yaml) ──
@@ -26319,6 +26624,29 @@ function checkPackage(dir, options = {}) {
26319
26624
  }
26320
26625
  }
26321
26626
  }
26627
+ {
26628
+ const m = standard.packageManifest;
26629
+ if (m && m.licenseKey !== void 0) {
26630
+ if (!/^std:[a-z0-9-]+$/.test(m.licenseKey)) {
26631
+ err(
26632
+ "C144",
26633
+ `package "${m.id}": license_key "${m.licenseKey}" is not a catalog key \u2014 the key shape is std: followed by a lowercase kebab id, ^std:[a-z0-9-]+$ (license-key-shape)`
26634
+ );
26635
+ }
26636
+ for (const dup of m.licenseKeyDuplicates ?? []) {
26637
+ err(
26638
+ "C145",
26639
+ `package "${m.id}": license_key declared again ("${dup}" before "${m.licenseKey}") \u2014 a package declares at most one license key; the last declaration wins (license-key-unique)`
26640
+ );
26641
+ }
26642
+ if (!m.licenseHolder) {
26643
+ err(
26644
+ "C146",
26645
+ `package "${m.id}": license_key "${m.licenseKey}" without a license_holder \u2014 a licensed package declares its copyright owner (license-holder-required)`
26646
+ );
26647
+ }
26648
+ }
26649
+ }
26322
26650
  {
26323
26651
  const schemeTypeIds = new Set((standard.schemeTypes ?? []).map((t) => t.id));
26324
26652
  const menuIds = new Set(
@@ -26739,6 +27067,97 @@ function checkPackage(dir, options = {}) {
26739
27067
  }
26740
27068
  }
26741
27069
  }
27070
+ {
27071
+ for (const ct of standard.conformanceTests ?? []) {
27072
+ const prep = ct.preparation;
27073
+ if (prep) {
27074
+ const orders = /* @__PURE__ */ new Set();
27075
+ for (const step of prep.entries) {
27076
+ const at = step.order !== null ? `preparation step ${step.order}` : "a preparation step";
27077
+ if (step.order === null || !Number.isInteger(step.order) || step.order < 1) {
27078
+ err(
27079
+ "C147",
27080
+ `conformance_test ${ct.id}: ${at} declares no positive-integer order (got ${step.order === null ? "none" : step.order}) \u2014 the step order is a positive integer (test-program-shape)`
27081
+ );
27082
+ } else if (orders.has(step.order)) {
27083
+ err(
27084
+ "C147",
27085
+ `conformance_test ${ct.id}: preparation step order ${step.order} is declared twice \u2014 the order is unique within the program (test-program-shape)`
27086
+ );
27087
+ } else {
27088
+ orders.add(step.order);
27089
+ }
27090
+ if (step.action === "" && step.drive === "") {
27091
+ err(
27092
+ "C147",
27093
+ `conformance_test ${ct.id}: ${at} declares neither action nor drive \u2014 a preparation step carries a prose action AND/OR a drive reference (test-program-shape)`
27094
+ );
27095
+ }
27096
+ if (step.verify && (step.verify.read === "" || step.verify.tolerance === "")) {
27097
+ err(
27098
+ "C147",
27099
+ `conformance_test ${ct.id}: ${at} declares a verify block without ${step.verify.read === "" ? "read" : "tolerance"} \u2014 the verification names what to read AND the tolerance reference (test-program-shape)`
27100
+ );
27101
+ }
27102
+ if (step.hold !== "" && parseFreshnessWindow(step.hold) === null) {
27103
+ err(
27104
+ "C148",
27105
+ `conformance_test ${ct.id}: ${at} hold "${step.hold}" is not a parseable duration (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT30M) (test-program-duration)`
27106
+ );
27107
+ }
27108
+ }
27109
+ }
27110
+ const stim = ct.stimulus;
27111
+ if (stim) {
27112
+ const orders = /* @__PURE__ */ new Set();
27113
+ for (const point of stim.entries) {
27114
+ const at = point.order !== null ? `stimulus point ${point.order}` : "a stimulus point";
27115
+ if (point.order === null || !Number.isInteger(point.order) || point.order < 1) {
27116
+ err(
27117
+ "C147",
27118
+ `conformance_test ${ct.id}: ${at} declares no positive-integer order (got ${point.order === null ? "none" : point.order}) \u2014 the point order is a positive integer (test-program-shape)`
27119
+ );
27120
+ } else if (orders.has(point.order)) {
27121
+ err(
27122
+ "C147",
27123
+ `conformance_test ${ct.id}: stimulus point order ${point.order} is declared twice \u2014 the order is unique within the program (test-program-shape)`
27124
+ );
27125
+ } else {
27126
+ orders.add(point.order);
27127
+ }
27128
+ if (point.drive === "") {
27129
+ err(
27130
+ "C147",
27131
+ `conformance_test ${ct.id}: ${at} declares no drive \u2014 a stimulus point names the drive operation (test-program-shape)`
27132
+ );
27133
+ }
27134
+ if (point.acceptance === "") {
27135
+ err(
27136
+ "C147",
27137
+ `conformance_test ${ct.id}: ${at} declares no acceptance \u2014 a stimulus point names the MPE/requirement node it is judged against (test-program-shape)`
27138
+ );
27139
+ }
27140
+ if (point.hold !== "" && parseFreshnessWindow(point.hold) === null) {
27141
+ err(
27142
+ "C148",
27143
+ `conformance_test ${ct.id}: ${at} hold "${point.hold}" is not a parseable duration (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT30M) (test-program-duration)`
27144
+ );
27145
+ }
27146
+ if (point.freshWithin === "") {
27147
+ err(
27148
+ "C148",
27149
+ `conformance_test ${ct.id}: ${at} declares no fresh_within \u2014 a measurement point's read carries its freshness bound (\xA714.12: no stale semantics) (test-program-duration)`
27150
+ );
27151
+ } else if (parseFreshnessWindow(point.freshWithin) === null) {
27152
+ err(
27153
+ "C148",
27154
+ `conformance_test ${ct.id}: ${at} fresh_within "${point.freshWithin}" is not a parseable freshness window (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT5S) (test-program-duration)`
27155
+ );
27156
+ }
27157
+ }
27158
+ }
27159
+ }
27160
+ }
26742
27161
  {
26743
27162
  for (const fu of standard.formulasUsed ?? []) {
26744
27163
  if (stripWrapping(fu.id) === "") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primmel/primmel",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "Primmel — parser, types, and serializer for the Primmel modelling language (successor to MMEL)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",