@primmel/primmel 1.19.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.
- package/dist/src/check-rules.d.ts.map +1 -1
- package/dist/src/check-rules.js +17 -0
- package/dist/src/check-rules.js.map +1 -1
- package/dist/src/check.d.ts.map +1 -1
- package/dist/src/check.js +96 -0
- package/dist/src/check.js.map +1 -1
- package/dist/src/ser-des/config/conformanceTest.d.ts.map +1 -1
- package/dist/src/ser-des/config/conformanceTest.js +310 -0
- package/dist/src/ser-des/config/conformanceTest.js.map +1 -1
- package/dist/src/types/ConformanceTest.d.ts +86 -0
- package/dist/src/types/ConformanceTest.d.ts.map +1 -1
- package/dist-browser/index.mjs +934 -821
- package/dist-cli/primmel.mjs +355 -0
- package/package.json +1 -1
package/dist-cli/primmel.mjs
CHANGED
|
@@ -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) {
|
|
@@ -22845,6 +23078,37 @@ var CHECK_RULES = [
|
|
|
22845
23078
|
"normal",
|
|
22846
23079
|
"smart architecture-gaps-2026-07.md E10, data/schemas/test-sequences.yaml"
|
|
22847
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
|
+
),
|
|
22848
23112
|
// ── the per-test evaluation-formula traces (smart gap-close E11,
|
|
22849
23113
|
// analysis/architecture-gaps-2026-07.md; the smart contract
|
|
22850
23114
|
// data/schemas/formulas-used.yaml) ──
|
|
@@ -26803,6 +27067,97 @@ function checkPackage(dir, options = {}) {
|
|
|
26803
27067
|
}
|
|
26804
27068
|
}
|
|
26805
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
|
+
}
|
|
26806
27161
|
{
|
|
26807
27162
|
for (const fu of standard.formulasUsed ?? []) {
|
|
26808
27163
|
if (stripWrapping(fu.id) === "") {
|
package/package.json
CHANGED