@hyperscale0/hsx 2.0.3 → 2.0.4
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/CHANGELOG.md +14 -0
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +9 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/std-bundle.js +1 -1
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +126 -5
- package/dist/src/typecheck.js.map +1 -1
- package/docs/llms-full.txt +8 -0
- package/docs/reference/diagnostics.md +8 -0
- package/package.json +3 -3
- package/src/diagnostics.ts +9 -0
- package/src/std-bundle.ts +1 -1
- package/src/typecheck.ts +170 -5
- package/std/money_flows/held_payment.hsx +23 -10
package/dist/src/typecheck.js
CHANGED
|
@@ -191,6 +191,7 @@ export function checkGeneralProgram(program, options = {}) {
|
|
|
191
191
|
concrete.push(...bindActionPorts(constructedInstruments(decl.name, body, diagnostics), scoped.ports, reachedPorts).map((candidate) => ({
|
|
192
192
|
...candidate,
|
|
193
193
|
aliases: scoped.aliases,
|
|
194
|
+
callee: candidate.name.name,
|
|
194
195
|
parties: scoped.parties,
|
|
195
196
|
})));
|
|
196
197
|
}
|
|
@@ -218,11 +219,19 @@ export function checkGeneralProgram(program, options = {}) {
|
|
|
218
219
|
const merged = decl.metadata
|
|
219
220
|
? mergeApplicationMetadata(body, decl.metadata, diagnostics)
|
|
220
221
|
: body;
|
|
221
|
-
concrete.push(...bindActionPorts(constructedInstruments(decl.name, merged, diagnostics), boundPorts, applicationPorts).map((candidate) =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
concrete.push(...bindActionPorts(constructedInstruments(decl.name, merged, diagnostics), boundPorts, applicationPorts).map((candidate) => {
|
|
223
|
+
let candidateCallee = callee;
|
|
224
|
+
if (candidate.name.name !== decl.name.name &&
|
|
225
|
+
candidate.name.name.startsWith(`${decl.name.name}_`)) {
|
|
226
|
+
candidateCallee = `${callee}${candidate.name.name.slice(decl.name.name.length)}`;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
...candidate,
|
|
230
|
+
aliases: scoped.aliases,
|
|
231
|
+
callee: candidateCallee,
|
|
232
|
+
parties: scoped.parties,
|
|
233
|
+
};
|
|
234
|
+
}));
|
|
226
235
|
for (const port of boundPorts.values()) {
|
|
227
236
|
if (applicationPorts.has(port))
|
|
228
237
|
reachedPorts.add(port);
|
|
@@ -237,6 +246,9 @@ export function checkGeneralProgram(program, options = {}) {
|
|
|
237
246
|
report("HSX1024", port.name.span, `wired decision port ${port.name.name} reaches no action`, `carry ${port.name.name}'s condition into the action it authorizes`);
|
|
238
247
|
}
|
|
239
248
|
const allocated = allocateGeneratedPrefixes(concrete);
|
|
249
|
+
if (options.publishedCatalog) {
|
|
250
|
+
checkPortCaptureTypes(allocated, options.publishedCatalog, aliases, diagnostics);
|
|
251
|
+
}
|
|
240
252
|
const instruments = [];
|
|
241
253
|
const ids = new Set();
|
|
242
254
|
for (const candidate of allocated) {
|
|
@@ -532,6 +544,115 @@ function publishedFieldType(schema, catalog) {
|
|
|
532
544
|
`^${instrument.idPrefix}_(sandbox|live)_[a-z0-9]{8,64}$`);
|
|
533
545
|
return target ? { kind: "ref", target: target.id } : { kind: "text" };
|
|
534
546
|
}
|
|
547
|
+
const STRING_BACKED_PORT_KINDS = new Set([
|
|
548
|
+
"account",
|
|
549
|
+
"condition",
|
|
550
|
+
"date",
|
|
551
|
+
"party",
|
|
552
|
+
"ref",
|
|
553
|
+
"text",
|
|
554
|
+
]);
|
|
555
|
+
const INTEGER_BACKED_PORT_KINDS = new Set([
|
|
556
|
+
"bps",
|
|
557
|
+
"integer",
|
|
558
|
+
"percent",
|
|
559
|
+
]);
|
|
560
|
+
/**
|
|
561
|
+
* A published schema keeps less than the HSX type: dates and accounts publish
|
|
562
|
+
* as plain strings, percents and bps as integers. Compare by the family the
|
|
563
|
+
* schema can still express so the check never rejects a richer declared type.
|
|
564
|
+
*/
|
|
565
|
+
function samePortType(declared, captured) {
|
|
566
|
+
if (captured.kind === "unknown" || declared.kind === "unknown")
|
|
567
|
+
return true;
|
|
568
|
+
if (captured.kind === "text")
|
|
569
|
+
return STRING_BACKED_PORT_KINDS.has(declared.kind);
|
|
570
|
+
if (captured.kind === "integer") {
|
|
571
|
+
return INTEGER_BACKED_PORT_KINDS.has(declared.kind);
|
|
572
|
+
}
|
|
573
|
+
if (declared.kind !== captured.kind)
|
|
574
|
+
return false;
|
|
575
|
+
if (captured.currency && declared.currency !== captured.currency) {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
if (captured.target && declared.target !== captured.target) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
function portTypeWords(type) {
|
|
584
|
+
if (type.kind === "ref" && type.target)
|
|
585
|
+
return `ref<${type.target}>`;
|
|
586
|
+
return typeWords(type);
|
|
587
|
+
}
|
|
588
|
+
function getFieldSchemaFromAction(action, fieldName) {
|
|
589
|
+
if (!action.captureInput)
|
|
590
|
+
return undefined;
|
|
591
|
+
const properties = action.input?.properties;
|
|
592
|
+
if (!properties)
|
|
593
|
+
return undefined;
|
|
594
|
+
for (const inputKey of Object.values(action.captureInput)) {
|
|
595
|
+
if (inputKey !== fieldName && camel(inputKey) !== camel(fieldName)) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const schema = properties[inputKey] ?? properties[camel(inputKey)];
|
|
599
|
+
if (schema && typeof schema === "object")
|
|
600
|
+
return schema;
|
|
601
|
+
}
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
function findCapturedFieldSchema(catalogInstrument, actionName, fieldName) {
|
|
605
|
+
const targetAction = catalogInstrument.actions[actionName];
|
|
606
|
+
return targetAction
|
|
607
|
+
? getFieldSchemaFromAction(targetAction, fieldName)
|
|
608
|
+
: undefined;
|
|
609
|
+
}
|
|
610
|
+
function checkPortCaptureTypes(candidates, publishedCatalog, topLevelAliases, diagnostics) {
|
|
611
|
+
const catalogById = new Map(publishedCatalog.instruments.map((instrument) => [
|
|
612
|
+
instrument.id,
|
|
613
|
+
instrument,
|
|
614
|
+
]));
|
|
615
|
+
for (const candidate of candidates) {
|
|
616
|
+
const catalogInstrument = catalogById.get(candidate.name.name) ??
|
|
617
|
+
(candidate.callee ? catalogById.get(candidate.callee) : undefined);
|
|
618
|
+
if (!catalogInstrument)
|
|
619
|
+
continue;
|
|
620
|
+
const aliases = new Map([...topLevelAliases, ...candidate.aliases]);
|
|
621
|
+
for (const [actionName, declaredPorts] of candidate.ports) {
|
|
622
|
+
for (const port of declaredPorts) {
|
|
623
|
+
const rawShape = entry(port.body, "shape")?.value;
|
|
624
|
+
if (rawShape?.kind !== "block")
|
|
625
|
+
continue;
|
|
626
|
+
for (const fieldEntry of rawShape.entries) {
|
|
627
|
+
const fieldName = fieldEntry.key.name;
|
|
628
|
+
const capturedSchema = findCapturedFieldSchema(catalogInstrument, actionName, fieldName);
|
|
629
|
+
if (!capturedSchema)
|
|
630
|
+
continue;
|
|
631
|
+
const capturedType = publishedFieldType(capturedSchema, publishedCatalog.instruments);
|
|
632
|
+
const declaredType = typeOf(fieldEntry.value, aliases);
|
|
633
|
+
if (!samePortType(declaredType, capturedType)) {
|
|
634
|
+
const declaredWords = portTypeWords(declaredType);
|
|
635
|
+
const capturedWords = portTypeWords(capturedType);
|
|
636
|
+
const message = `decision port ${port.name.name} field ${fieldName} declares ${declaredWords} but instrument ${catalogInstrument.id} captures it as ${capturedWords}`;
|
|
637
|
+
const fix = `declare ${fieldName} as ${capturedWords} in decision port ${port.name.name}`;
|
|
638
|
+
if (!diagnostics.some((prior) => prior.code === "HSX1026" &&
|
|
639
|
+
prior.span.start === fieldEntry.value.span.start &&
|
|
640
|
+
prior.span.end === fieldEntry.value.span.end &&
|
|
641
|
+
prior.message === message)) {
|
|
642
|
+
diagnostics.push({
|
|
643
|
+
code: "HSX1026",
|
|
644
|
+
fix,
|
|
645
|
+
message,
|
|
646
|
+
severity: "error",
|
|
647
|
+
span: fieldEntry.value.span,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
535
656
|
function typedPublishedSubject(subject, origin) {
|
|
536
657
|
return {
|
|
537
658
|
declaredValue: subject.declaredValue,
|