@hyperscale0/hsx 2.0.4 → 2.1.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/CHANGELOG.md +23 -0
- package/README.md +17 -3
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +6 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +2 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +15 -2
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +22 -6
- package/dist/src/cost.js.map +1 -1
- package/dist/src/diagnostics.d.ts +2 -0
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +60 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/emit.d.ts +16 -1
- package/dist/src/emit.d.ts.map +1 -1
- package/dist/src/emit.js +49 -11
- package/dist/src/emit.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/ir.d.ts +8 -1
- package/dist/src/ir.d.ts.map +1 -1
- package/dist/src/ir.js.map +1 -1
- package/dist/src/limits.d.ts +2 -0
- package/dist/src/limits.d.ts.map +1 -1
- package/dist/src/limits.js +2 -0
- package/dist/src/limits.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +123 -15
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/assets/brand/hsx-horizontal-white.svg +1 -0
- package/docs/assets/brand/hsx-horizontal.svg +1 -0
- package/docs/assets/brand/hsx-stacked-white.svg +1 -0
- package/docs/assets/brand/hsx-stacked.svg +1 -0
- package/docs/assets/brand/manifest.json +30 -0
- package/docs/assets/hsx.svg +8 -15
- package/docs/llms-full.txt +51 -3
- package/docs/llms.txt +2 -2
- package/docs/reference/cli.md +2 -2
- package/docs/reference/diagnostics.md +49 -1
- package/docs/reference/grammar.md +2 -2
- package/docs/reference/std/advance.md +1 -1
- package/docs/reference/std/cancellable_booking.md +1 -1
- package/docs/reference/std/captured_payment.md +1 -1
- package/docs/reference/std/conditional_disbursement.md +1 -1
- package/docs/reference/std/credit_facility.md +1 -1
- package/docs/reference/std/held_payment.md +1 -1
- package/docs/reference/std/instant_transfer.md +1 -1
- package/docs/reference/std/metered.md +1 -1
- package/docs/reference/std/pooled_split.md +1 -1
- package/docs/reference/std/premium_forward.md +1 -1
- package/docs/reference/std/reconciled_payout.md +1 -1
- package/docs/reference/std/recurring_collection.md +1 -1
- package/docs/reference/std/rotating_pool.md +1 -1
- package/docs/reference/std/scheduled.md +1 -1
- package/docs/reference/std/security_deposit.md +1 -1
- package/docs/reference/std/settlement_batch.md +1 -1
- package/docs/reference/std/swap.md +1 -1
- package/docs/reference/std/threshold_pool.md +1 -1
- package/docs/reference/std/weighted_distribution.md +1 -1
- package/docs/reference/types.md +1 -1
- package/docs/reference/udl-output.md +1 -1
- package/package.json +3 -3
- package/src/cli.ts +8 -5
- package/src/compile.ts +17 -2
- package/src/cost.ts +32 -5
- package/src/diagnostics.ts +67 -0
- package/src/emit.ts +82 -15
- package/src/index.ts +6 -1
- package/src/ir.ts +15 -1
- package/src/limits.ts +2 -0
- package/src/typecheck.ts +147 -16
- package/src/version.ts +1 -1
package/src/cost.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
udlEffectKinds,
|
|
3
|
+
resolveUdlActionPlans,
|
|
3
4
|
type UdlDocument,
|
|
4
5
|
type UdlEffectKind,
|
|
5
6
|
} from "@hyperscale0/udl";
|
|
@@ -247,8 +248,17 @@ export function buildUdlCostManifest(
|
|
|
247
248
|
const rowsBySignature = indexCostRows(table);
|
|
248
249
|
const actions: UdlCostManifest["actions"][number][] = [];
|
|
249
250
|
for (const instrument of document.instruments) {
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
const resolved = resolveUdlActionPlans(instrument);
|
|
252
|
+
if (resolved.issues.length > 0)
|
|
253
|
+
throw new Error(resolved.issues[0]!.message);
|
|
254
|
+
for (const actionName of instrument.actionOrder) {
|
|
255
|
+
const action = instrument.actions[actionName]!;
|
|
256
|
+
const plan = resolved.plans.find(
|
|
257
|
+
(candidate) => candidate.action === actionName,
|
|
258
|
+
);
|
|
259
|
+
const effects = (
|
|
260
|
+
action.calls && plan ? plan.effects : (action.effects ?? {})
|
|
261
|
+
) as Readonly<
|
|
252
262
|
Partial<
|
|
253
263
|
Record<
|
|
254
264
|
HsxEffectKind,
|
|
@@ -526,7 +536,15 @@ function typedStructuralCounts(
|
|
|
526
536
|
aggregate += arrayValue(instrument.slots.aggregateInvariants).length;
|
|
527
537
|
for (const action of instrument.actions) {
|
|
528
538
|
flow += 1;
|
|
529
|
-
|
|
539
|
+
const plan = instrument.actionPlans?.find(
|
|
540
|
+
(plan) => plan.action === action.name,
|
|
541
|
+
);
|
|
542
|
+
move +=
|
|
543
|
+
action.slots.calls && plan
|
|
544
|
+
? plan.leaves.filter((leaf) =>
|
|
545
|
+
leaf.step.operation.startsWith("internal_transfer."),
|
|
546
|
+
).length
|
|
547
|
+
: arrayValue(action.slots.moves).length;
|
|
530
548
|
gate += arrayValue(action.slots.requiresRefs).length;
|
|
531
549
|
if (action.slots.deadline !== undefined) timer += 1;
|
|
532
550
|
if (action.slots.due !== undefined) timer += 1;
|
|
@@ -565,9 +583,18 @@ function udlStructuralCounts(
|
|
|
565
583
|
if (Object.values(instrument.actions).some((action) => action.quote))
|
|
566
584
|
unwind += 1;
|
|
567
585
|
aggregate += instrument.aggregateInvariants?.length ?? 0;
|
|
568
|
-
|
|
586
|
+
const resolved = resolveUdlActionPlans(instrument);
|
|
587
|
+
if (resolved.issues.length > 0)
|
|
588
|
+
throw new Error(resolved.issues[0]!.message);
|
|
589
|
+
for (const [name, action] of Object.entries(instrument.actions)) {
|
|
569
590
|
flow += 1;
|
|
570
|
-
|
|
591
|
+
const plan = resolved.plans.find((plan) => plan.action === name);
|
|
592
|
+
move +=
|
|
593
|
+
action.calls && plan
|
|
594
|
+
? plan.leaves.filter((leaf) =>
|
|
595
|
+
leaf.step.operation.startsWith("internal_transfer."),
|
|
596
|
+
).length
|
|
597
|
+
: action.moves.length;
|
|
571
598
|
gate += action.requiresRefs?.length ?? 0;
|
|
572
599
|
if (action.deadline) timer += 1;
|
|
573
600
|
if (action.due) timer += 1;
|
package/src/diagnostics.ts
CHANGED
|
@@ -543,4 +543,71 @@ instrument probe {
|
|
|
543
543
|
fix: "Correct compile-time block keys or parameter bindings.",
|
|
544
544
|
example: instrument('title: "__hsx_none__";'),
|
|
545
545
|
},
|
|
546
|
+
{
|
|
547
|
+
code: "HSX1610",
|
|
548
|
+
stage: "typecheck",
|
|
549
|
+
title: "Invalid piece partition",
|
|
550
|
+
fix: "Bind immutable money and account fields to one declared partition.",
|
|
551
|
+
example: null,
|
|
552
|
+
reason:
|
|
553
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
code: "HSX1611",
|
|
557
|
+
stage: "typecheck",
|
|
558
|
+
title: "Invalid piece stage",
|
|
559
|
+
fix: "Use a declared piece stage and let the compiler derive pieceId.",
|
|
560
|
+
example: null,
|
|
561
|
+
reason:
|
|
562
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
code: "HSX1612",
|
|
566
|
+
stage: "typecheck",
|
|
567
|
+
title: "Invalid action graph",
|
|
568
|
+
fix: "Declare an acyclic action graph within the expansion bound.",
|
|
569
|
+
example: null,
|
|
570
|
+
reason:
|
|
571
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
code: "HSX1613",
|
|
575
|
+
stage: "typecheck",
|
|
576
|
+
title: "Invalid static call binding",
|
|
577
|
+
fix: "Bind declared typed targets with unique captures and money consumption.",
|
|
578
|
+
example: null,
|
|
579
|
+
reason:
|
|
580
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
code: "HSX1614",
|
|
584
|
+
stage: "typecheck",
|
|
585
|
+
title: "Incompatible action boundary",
|
|
586
|
+
fix: "Keep the parent principal, approval and recovery boundary.",
|
|
587
|
+
example: null,
|
|
588
|
+
reason:
|
|
589
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
code: "HSX1615",
|
|
593
|
+
stage: "typecheck",
|
|
594
|
+
title: "Invalid leaf evidence or effects",
|
|
595
|
+
fix: "Declare evidence and the exact effects of each expanded leaf.",
|
|
596
|
+
example: null,
|
|
597
|
+
reason:
|
|
598
|
+
"The shared UDL action-plan validator supplies the code and source path.",
|
|
599
|
+
},
|
|
546
600
|
] as const;
|
|
601
|
+
|
|
602
|
+
/** Preserve the UDL diagnostic family when reporting a typed clause. */
|
|
603
|
+
export function hsxClauseDiagnosticCode(code: string): string {
|
|
604
|
+
const codes: Readonly<Record<string, string>> = {
|
|
605
|
+
UDL4002: "HSX1610",
|
|
606
|
+
UDL5013: "HSX1611",
|
|
607
|
+
UDL2010: "HSX1612",
|
|
608
|
+
UDL2011: "HSX1613",
|
|
609
|
+
UDL2012: "HSX1614",
|
|
610
|
+
UDL2013: "HSX1615",
|
|
611
|
+
};
|
|
612
|
+
return codes[code] ?? "HSX1602";
|
|
613
|
+
}
|
package/src/emit.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validateUdl,
|
|
3
|
+
resolveUdlActionPlans,
|
|
4
|
+
udlClauseVocabulary,
|
|
3
5
|
type UdlDocument,
|
|
4
6
|
type UdlIssueCode,
|
|
5
7
|
} from "@hyperscale0/udl";
|
|
6
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
JsonValue,
|
|
10
|
+
TypedInstrument,
|
|
11
|
+
TypedProgram,
|
|
12
|
+
ResolvedProgramActionPlan,
|
|
13
|
+
} from "./ir.ts";
|
|
14
|
+
|
|
15
|
+
import { hsxClauseDiagnosticCode } from "./diagnostics.ts";
|
|
7
16
|
|
|
8
17
|
export interface OriginMapEntry {
|
|
9
18
|
readonly path: string;
|
|
@@ -25,24 +34,14 @@ export type GeneralLowerResult =
|
|
|
25
34
|
readonly ok: true;
|
|
26
35
|
readonly value: {
|
|
27
36
|
readonly document: UdlDocument;
|
|
37
|
+
readonly actionPlans?: readonly ResolvedProgramActionPlan[];
|
|
28
38
|
readonly originMap: readonly OriginMapEntry[];
|
|
29
39
|
};
|
|
30
40
|
};
|
|
31
41
|
|
|
32
42
|
export function lowerGeneralProgram(program: TypedProgram): GeneralLowerResult {
|
|
33
43
|
const candidate = {
|
|
34
|
-
instruments: program.instruments.map(
|
|
35
|
-
...instrument.slots,
|
|
36
|
-
actionOrder: instrument.actions.map((action) => action.name),
|
|
37
|
-
actions: Object.fromEntries(
|
|
38
|
-
instrument.actions.map((action) => [action.name, action.slots]),
|
|
39
|
-
),
|
|
40
|
-
fields: Object.fromEntries(
|
|
41
|
-
instrument.fields.map((field) => [field.name, field.schema]),
|
|
42
|
-
),
|
|
43
|
-
id: instrument.id,
|
|
44
|
-
required: requiredFields(instrument),
|
|
45
|
-
})),
|
|
44
|
+
instruments: program.instruments.map(generalInstrumentValue),
|
|
46
45
|
product: program.name,
|
|
47
46
|
subjects: program.subjects.map((subject) => ({
|
|
48
47
|
declaredValue: subject.declaredValue,
|
|
@@ -79,8 +78,11 @@ export function lowerGeneralProgram(program: TypedProgram): GeneralLowerResult {
|
|
|
79
78
|
issues: validation.issues.map((issue) => {
|
|
80
79
|
const origin = originForUdlPath(issue.path, origins);
|
|
81
80
|
return {
|
|
82
|
-
code:
|
|
83
|
-
|
|
81
|
+
code:
|
|
82
|
+
issue.category === "invalid_semantics"
|
|
83
|
+
? hsxClauseDiagnosticCode(issue.code)
|
|
84
|
+
: "HSX1601",
|
|
85
|
+
fix: issue.fix,
|
|
84
86
|
message: `${issue.path}: ${issue.message}`,
|
|
85
87
|
path: issue.path,
|
|
86
88
|
span: origin?.span ?? program.origin,
|
|
@@ -91,10 +93,17 @@ export function lowerGeneralProgram(program: TypedProgram): GeneralLowerResult {
|
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
const document = validation.value;
|
|
96
|
+
const actionPlans = document.instruments.flatMap((instrument) =>
|
|
97
|
+
resolveUdlActionPlans(instrument).plans.map((plan) => ({
|
|
98
|
+
...plan,
|
|
99
|
+
instrument: instrument.id,
|
|
100
|
+
})),
|
|
101
|
+
);
|
|
94
102
|
return {
|
|
95
103
|
ok: true,
|
|
96
104
|
value: {
|
|
97
105
|
document,
|
|
106
|
+
...(actionPlans.length > 0 ? { actionPlans } : {}),
|
|
98
107
|
originMap: originMapFor(program),
|
|
99
108
|
},
|
|
100
109
|
};
|
|
@@ -213,3 +222,61 @@ function originMapFor(program: TypedProgram): OriginMapEntry[] {
|
|
|
213
222
|
});
|
|
214
223
|
return entries;
|
|
215
224
|
}
|
|
225
|
+
|
|
226
|
+
/** Emit a canonical clause value through the same generic HSX block syntax. */
|
|
227
|
+
export function emitUdlClause(
|
|
228
|
+
scope: "instrument" | "action",
|
|
229
|
+
target: string,
|
|
230
|
+
value: JsonValue,
|
|
231
|
+
): string {
|
|
232
|
+
const clause = udlClauseVocabulary.find(
|
|
233
|
+
(entry) => entry.scope === scope && entry.target === target,
|
|
234
|
+
);
|
|
235
|
+
if (!clause) throw new Error(`Unknown ${scope} clause ${target}`);
|
|
236
|
+
return `${clause.spelling.replaceAll(" ", "_")}: ${clauseLiteral(value)};`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function clauseLiteral(value: JsonValue): string {
|
|
240
|
+
if (Array.isArray(value)) return `[${value.map(clauseLiteral).join(", ")}]`;
|
|
241
|
+
if (value !== null && typeof value === "object") {
|
|
242
|
+
return `{ ${Object.entries(value)
|
|
243
|
+
.map(([key, item]) => `${JSON.stringify(key)}: ${clauseLiteral(item)};`)
|
|
244
|
+
.join(" ")} }`;
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
value === null ||
|
|
248
|
+
(typeof value === "number" && !Number.isSafeInteger(value))
|
|
249
|
+
) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
"HSX clause literals require non-null values and safe integers",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return JSON.stringify(value);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function hasActionPlanClauses(instrument: TypedInstrument): boolean {
|
|
258
|
+
return (
|
|
259
|
+
instrument.slots.piecePlan !== undefined ||
|
|
260
|
+
instrument.slots.actionLibrary !== undefined ||
|
|
261
|
+
instrument.actions.some(
|
|
262
|
+
(action) =>
|
|
263
|
+
action.slots.calls !== undefined ||
|
|
264
|
+
action.slots.pieceStage !== undefined,
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function generalInstrumentValue(instrument: TypedInstrument) {
|
|
270
|
+
return {
|
|
271
|
+
...instrument.slots,
|
|
272
|
+
actionOrder: instrument.actions.map((action) => action.name),
|
|
273
|
+
actions: Object.fromEntries(
|
|
274
|
+
instrument.actions.map((action) => [action.name, action.slots]),
|
|
275
|
+
),
|
|
276
|
+
fields: Object.fromEntries(
|
|
277
|
+
instrument.fields.map((field) => [field.name, field.schema]),
|
|
278
|
+
),
|
|
279
|
+
id: instrument.id,
|
|
280
|
+
required: requiredFields(instrument),
|
|
281
|
+
};
|
|
282
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -28,7 +28,11 @@ export {
|
|
|
28
28
|
type ProgramEntryOverrideValue,
|
|
29
29
|
} from "./entry-overrides.ts";
|
|
30
30
|
export { type OriginMapEntry } from "./emit.ts";
|
|
31
|
-
export {
|
|
31
|
+
export {
|
|
32
|
+
lowerGeneralProgram,
|
|
33
|
+
originForUdlPath,
|
|
34
|
+
emitUdlClause,
|
|
35
|
+
} from "./emit.ts";
|
|
32
36
|
export { parseProgram } from "./parse.ts";
|
|
33
37
|
export { checkGeneralProgram, type GeneralCheckOptions } from "./typecheck.ts";
|
|
34
38
|
export {
|
|
@@ -40,3 +44,4 @@ export {
|
|
|
40
44
|
type UseDecl,
|
|
41
45
|
} from "./ast.ts";
|
|
42
46
|
export { bundledStandardLibrary, type StandardLibrary } from "./std-library.ts";
|
|
47
|
+
export type { ResolvedActionPlan, ResolvedProgramActionPlan } from "./ir.ts";
|
package/src/ir.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Span } from "./ast.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
resolveUdlActionPlans,
|
|
4
|
+
UdlEffectKind,
|
|
5
|
+
UdlIssueCode,
|
|
6
|
+
} from "@hyperscale0/udl";
|
|
3
7
|
|
|
4
8
|
export type JsonValue =
|
|
5
9
|
| boolean
|
|
@@ -59,7 +63,15 @@ export interface TypedAction {
|
|
|
59
63
|
readonly slots: Readonly<Record<string, JsonValue>>;
|
|
60
64
|
}
|
|
61
65
|
|
|
66
|
+
export type ResolvedActionPlan = ReturnType<
|
|
67
|
+
typeof resolveUdlActionPlans
|
|
68
|
+
>["plans"][number];
|
|
69
|
+
export type ResolvedProgramActionPlan = ResolvedActionPlan & {
|
|
70
|
+
readonly instrument: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
62
73
|
export interface TypedInstrument {
|
|
74
|
+
readonly actionPlans?: readonly ResolvedActionPlan[];
|
|
63
75
|
readonly actions: readonly TypedAction[];
|
|
64
76
|
readonly fields: readonly TypedField[];
|
|
65
77
|
readonly id: string;
|
|
@@ -86,6 +98,8 @@ export interface TypedProgram {
|
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
export interface GeneralDiagnostic {
|
|
101
|
+
readonly path?: string;
|
|
102
|
+
readonly udlCode?: UdlIssueCode;
|
|
89
103
|
readonly code: string;
|
|
90
104
|
readonly fix: string;
|
|
91
105
|
readonly message: string;
|
package/src/limits.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* on. The deepest nests 5 levels; the largest is 13,934 bytes.
|
|
7
7
|
*/
|
|
8
8
|
export const HSX_LIMITS = Object.freeze({
|
|
9
|
+
/** Shared ceiling for finite comprehensions and action-call expansion. */
|
|
10
|
+
maxExpansions: 256,
|
|
9
11
|
/**
|
|
10
12
|
* How deep the recursive-descent productions may nest. Measured on this
|
|
11
13
|
* machine, the parser exhausted the call stack at 9,217 nested blocks and
|
package/src/typecheck.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deriveUdlActionEffects,
|
|
3
|
+
resolveUdlActionPlans,
|
|
4
|
+
udlDocumentSchema,
|
|
3
5
|
udlClauseVocabulary,
|
|
4
6
|
type UdlAction,
|
|
5
7
|
type UdlDocument,
|
|
@@ -24,7 +26,14 @@ import type {
|
|
|
24
26
|
SubjectDecl,
|
|
25
27
|
UseDecl,
|
|
26
28
|
} from "./ast.ts";
|
|
27
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
requiredFields,
|
|
31
|
+
generalInstrumentValue,
|
|
32
|
+
hasActionPlanClauses,
|
|
33
|
+
lowerGeneralProgram,
|
|
34
|
+
} from "./emit.ts";
|
|
35
|
+
import { hsxClauseDiagnosticCode } from "./diagnostics.ts";
|
|
36
|
+
import { HSX_LIMITS } from "./limits.ts";
|
|
28
37
|
import type {
|
|
29
38
|
GeneralCheckResult,
|
|
30
39
|
GeneralDiagnostic,
|
|
@@ -43,7 +52,7 @@ const CURRENCY = /^[A-Z]{3}$/;
|
|
|
43
52
|
const MONEY_PATTERN = "^[1-9][0-9]{0,17}$";
|
|
44
53
|
const OPTIONAL_MONEY_PATTERN = "^(0|[1-9][0-9]{0,17})$";
|
|
45
54
|
const ACCOUNT_PATTERN = "^acct_(sandbox|live)_[a-z0-9]{8,64}$";
|
|
46
|
-
|
|
55
|
+
|
|
47
56
|
const NONE_SENTINEL = "__hsx_none__";
|
|
48
57
|
|
|
49
58
|
export interface GeneralCheckOptions {
|
|
@@ -517,7 +526,7 @@ export function checkGeneralProgram(
|
|
|
517
526
|
subjects,
|
|
518
527
|
title: header?.title?.value ?? sentenceCase(name),
|
|
519
528
|
};
|
|
520
|
-
return
|
|
529
|
+
return prepareActionPlanProgram(typed, diagnostics);
|
|
521
530
|
}
|
|
522
531
|
|
|
523
532
|
function checkPublishedProgram(
|
|
@@ -766,7 +775,7 @@ function checkPublishedProgram(
|
|
|
766
775
|
],
|
|
767
776
|
title: header?.title?.value ?? sentenceCase(name),
|
|
768
777
|
};
|
|
769
|
-
return
|
|
778
|
+
return prepareActionPlanProgram(typed, diagnostics);
|
|
770
779
|
}
|
|
771
780
|
|
|
772
781
|
function projectedPublishedInstrument(
|
|
@@ -3014,11 +3023,11 @@ function expandComprehensions(
|
|
|
3014
3023
|
});
|
|
3015
3024
|
continue;
|
|
3016
3025
|
}
|
|
3017
|
-
if (budget.used + values.length >
|
|
3026
|
+
if (budget.used + values.length > HSX_LIMITS.maxExpansions) {
|
|
3018
3027
|
diagnostics.push({
|
|
3019
3028
|
code: "HSX1404",
|
|
3020
|
-
fix: `reduce the fixed expansion to at most ${
|
|
3021
|
-
message: `bounded comprehensions expand past the ${
|
|
3029
|
+
fix: `reduce the fixed expansion to at most ${HSX_LIMITS.maxExpansions} rows`,
|
|
3030
|
+
message: `bounded comprehensions expand past the ${HSX_LIMITS.maxExpansions}-row compiler limit`,
|
|
3022
3031
|
severity: "error",
|
|
3023
3032
|
span: bound.span,
|
|
3024
3033
|
});
|
|
@@ -3481,7 +3490,10 @@ function checkInstrument(
|
|
|
3481
3490
|
];
|
|
3482
3491
|
}
|
|
3483
3492
|
|
|
3484
|
-
return
|
|
3493
|
+
return derivePieceInputs(
|
|
3494
|
+
{ actions, fields, id: name.name, origin: name.span, slots },
|
|
3495
|
+
diagnostics,
|
|
3496
|
+
);
|
|
3485
3497
|
}
|
|
3486
3498
|
|
|
3487
3499
|
// Mirrors the catalog law agent_description_required: every action a caller
|
|
@@ -4787,6 +4799,7 @@ function blockToObject(
|
|
|
4787
4799
|
block: BlockExpr | undefined,
|
|
4788
4800
|
substitutions: ReadonlyMap<string, Expr>,
|
|
4789
4801
|
diagnostics: GeneralDiagnostic[],
|
|
4802
|
+
literalKeys = false,
|
|
4790
4803
|
): Record<string, JsonValue> {
|
|
4791
4804
|
if (!block) return {};
|
|
4792
4805
|
const result: Record<string, JsonValue> = {};
|
|
@@ -4802,10 +4815,8 @@ function blockToObject(
|
|
|
4802
4815
|
});
|
|
4803
4816
|
continue;
|
|
4804
4817
|
}
|
|
4805
|
-
result[row.key.quoted ? row.key.name : camel(row.key.name)] =
|
|
4806
|
-
value,
|
|
4807
|
-
diagnostics,
|
|
4808
|
-
);
|
|
4818
|
+
result[literalKeys || row.key.quoted ? row.key.name : camel(row.key.name)] =
|
|
4819
|
+
exprToJson(value, diagnostics, literalKeys);
|
|
4809
4820
|
}
|
|
4810
4821
|
return result;
|
|
4811
4822
|
}
|
|
@@ -4832,10 +4843,21 @@ function exprToJsonForUdlSlot(
|
|
|
4832
4843
|
]),
|
|
4833
4844
|
);
|
|
4834
4845
|
}
|
|
4835
|
-
|
|
4846
|
+
// Declaration maps retain their authored keys; field-spelling aliases still normalize.
|
|
4847
|
+
const literalKeys = udlClauseVocabulary.some(
|
|
4848
|
+
(clause) =>
|
|
4849
|
+
clause.target === slot &&
|
|
4850
|
+
"literalKeys" in clause &&
|
|
4851
|
+
clause.literalKeys === true,
|
|
4852
|
+
);
|
|
4853
|
+
return exprToJson(expr, diagnostics, literalKeys);
|
|
4836
4854
|
}
|
|
4837
4855
|
|
|
4838
|
-
function exprToJson(
|
|
4856
|
+
function exprToJson(
|
|
4857
|
+
expr: Expr,
|
|
4858
|
+
diagnostics: GeneralDiagnostic[],
|
|
4859
|
+
literalKeys = false,
|
|
4860
|
+
): JsonValue {
|
|
4839
4861
|
switch (expr.kind) {
|
|
4840
4862
|
case "boolean":
|
|
4841
4863
|
return expr.value;
|
|
@@ -4880,9 +4902,11 @@ function exprToJson(expr: Expr, diagnostics: GeneralDiagnostic[]): JsonValue {
|
|
|
4880
4902
|
case "settlement_ref":
|
|
4881
4903
|
return `${expr.owner.name}.${expr.member.name}`;
|
|
4882
4904
|
case "list":
|
|
4883
|
-
return expr.items.map((item) =>
|
|
4905
|
+
return expr.items.map((item) =>
|
|
4906
|
+
exprToJson(item, diagnostics, literalKeys),
|
|
4907
|
+
);
|
|
4884
4908
|
case "block":
|
|
4885
|
-
return blockToObject(expr, new Map(), diagnostics);
|
|
4909
|
+
return blockToObject(expr, new Map(), diagnostics, literalKeys);
|
|
4886
4910
|
case "call":
|
|
4887
4911
|
return `${expr.callee.name}(${expr.args.map((arg) => String(exprToJson(arg, diagnostics))).join(",")})`;
|
|
4888
4912
|
case "type_apply":
|
|
@@ -5055,3 +5079,110 @@ function sentenceCase(value: string): string {
|
|
|
5055
5079
|
const words = value.replaceAll("_", " ");
|
|
5056
5080
|
return `${words[0]?.toUpperCase() ?? ""}${words.slice(1)}`;
|
|
5057
5081
|
}
|
|
5082
|
+
|
|
5083
|
+
function prepareActionPlanProgram(
|
|
5084
|
+
program: TypedProgram,
|
|
5085
|
+
diagnostics: GeneralDiagnostic[],
|
|
5086
|
+
): GeneralCheckResult {
|
|
5087
|
+
if (!program.instruments.some(hasActionPlanClauses))
|
|
5088
|
+
return { diagnostics, program };
|
|
5089
|
+
const instruments = program.instruments.map((instrument): TypedInstrument => {
|
|
5090
|
+
if (!hasActionPlanClauses(instrument)) return instrument;
|
|
5091
|
+
const parsed = udlDocumentSchema.shape.instruments.element.safeParse(
|
|
5092
|
+
generalInstrumentValue(instrument),
|
|
5093
|
+
);
|
|
5094
|
+
if (!parsed.success) {
|
|
5095
|
+
for (const issue of parsed.error.issues)
|
|
5096
|
+
diagnostics.push({
|
|
5097
|
+
code: "HSX1601",
|
|
5098
|
+
severity: "error",
|
|
5099
|
+
span: instrument.origin,
|
|
5100
|
+
message: `${instrument.id}.${issue.path.join(".")}: ${issue.message}`,
|
|
5101
|
+
fix: "match the typed UDL clause declaration",
|
|
5102
|
+
});
|
|
5103
|
+
return instrument;
|
|
5104
|
+
}
|
|
5105
|
+
const resolved = resolveUdlActionPlans(parsed.data);
|
|
5106
|
+
for (const issue of resolved.issues)
|
|
5107
|
+
diagnostics.push({
|
|
5108
|
+
code: hsxClauseDiagnosticCode(issue.code),
|
|
5109
|
+
severity: "error",
|
|
5110
|
+
span: instrument.origin,
|
|
5111
|
+
message: `${instrument.id}${issue.path}: ${issue.message}`,
|
|
5112
|
+
udlCode: issue.code,
|
|
5113
|
+
path: issue.path,
|
|
5114
|
+
fix: issue.fix,
|
|
5115
|
+
});
|
|
5116
|
+
return {
|
|
5117
|
+
...instrument,
|
|
5118
|
+
actionPlans: resolved.plans,
|
|
5119
|
+
actions: instrument.actions.map((action) => {
|
|
5120
|
+
if (!action.slots.calls) return action;
|
|
5121
|
+
const plan = resolved.plans.find((plan) => plan.action === action.name);
|
|
5122
|
+
if (!plan) return action;
|
|
5123
|
+
const effects = plan.effects;
|
|
5124
|
+
return {
|
|
5125
|
+
...action,
|
|
5126
|
+
effects,
|
|
5127
|
+
slots: { ...action.slots, effects: effects as JsonValue },
|
|
5128
|
+
};
|
|
5129
|
+
}),
|
|
5130
|
+
};
|
|
5131
|
+
});
|
|
5132
|
+
if (diagnostics.some((issue) => issue.severity === "error"))
|
|
5133
|
+
return { diagnostics };
|
|
5134
|
+
const prepared = { ...program, instruments };
|
|
5135
|
+
const lowered = lowerGeneralProgram(prepared);
|
|
5136
|
+
if (!lowered.ok) {
|
|
5137
|
+
for (const issue of lowered.issues)
|
|
5138
|
+
diagnostics.push({
|
|
5139
|
+
code: issue.code,
|
|
5140
|
+
fix: issue.fix,
|
|
5141
|
+
message: issue.message,
|
|
5142
|
+
udlCode: issue.udlCode,
|
|
5143
|
+
path: issue.path,
|
|
5144
|
+
severity: "error",
|
|
5145
|
+
span: issue.span,
|
|
5146
|
+
});
|
|
5147
|
+
return { diagnostics };
|
|
5148
|
+
}
|
|
5149
|
+
return { diagnostics, program: prepared };
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
function derivePieceInputs(
|
|
5153
|
+
instrument: TypedInstrument,
|
|
5154
|
+
diagnostics: GeneralDiagnostic[],
|
|
5155
|
+
): TypedInstrument {
|
|
5156
|
+
return {
|
|
5157
|
+
...instrument,
|
|
5158
|
+
actions: instrument.actions.map((action) => {
|
|
5159
|
+
const stage = jsonObject(action.slots.pieceStage);
|
|
5160
|
+
if (!stage) return action;
|
|
5161
|
+
if (action.slots.input !== undefined) {
|
|
5162
|
+
diagnostics.push({
|
|
5163
|
+
code: "HSX1611",
|
|
5164
|
+
severity: "error",
|
|
5165
|
+
span: action.origin,
|
|
5166
|
+
message: `action ${action.name} overrides its compiler-derived pieceId input`,
|
|
5167
|
+
fix: "remove input; piece_stage derives the strict pieceId enum",
|
|
5168
|
+
});
|
|
5169
|
+
return action;
|
|
5170
|
+
}
|
|
5171
|
+
const plan = jsonObject(instrument.slots.piecePlan);
|
|
5172
|
+
const order = plan?.[`${stage.stage}_order`];
|
|
5173
|
+
if (!Array.isArray(order) || order.length === 0) return action;
|
|
5174
|
+
return {
|
|
5175
|
+
...action,
|
|
5176
|
+
slots: {
|
|
5177
|
+
...action.slots,
|
|
5178
|
+
input: {
|
|
5179
|
+
type: "object",
|
|
5180
|
+
additionalProperties: false,
|
|
5181
|
+
required: ["pieceId"],
|
|
5182
|
+
properties: { pieceId: { type: "string", enum: order } },
|
|
5183
|
+
},
|
|
5184
|
+
},
|
|
5185
|
+
};
|
|
5186
|
+
}),
|
|
5187
|
+
};
|
|
5188
|
+
}
|
package/src/version.ts
CHANGED