@burdenoff/microfe-movethewheels 2026.1010.2 → 2026.1011.1

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.
@@ -0,0 +1,34 @@
1
+ import "../src/generated/tracking-quality-operations.js";
2
+ //#region src/movethewheels/hooks/useTrackingEventQuality.ts
3
+ var e = new Set([
4
+ "ACCEPTED",
5
+ "DUPLICATE",
6
+ "CONFLICT",
7
+ "REJECTED"
8
+ ]), t = new Set([
9
+ "KEY_CONFLICT",
10
+ "BUDGET_EXHAUSTED",
11
+ "INTEGRATION_UNAVAILABLE",
12
+ "SHIPMENT_UNAVAILABLE",
13
+ "UPDATE_REJECTED"
14
+ ]), n = (e, t) => typeof e == "string" && e.length <= t, r = (e, t) => e === null || n(e, t);
15
+ function i(i) {
16
+ if (!Array.isArray(i) || i.length > 5) return null;
17
+ let a = [];
18
+ for (let o of i) {
19
+ if (typeof o != "object" || !o || Array.isArray(o)) return null;
20
+ let i = o;
21
+ if (!Number.isInteger(i.index) || typeof i.index != "number" || i.index !== a.length || i.index < 0 || i.index > 4 || !r(i.externalEventId, 255) || !n(i.shipmentReference, 255) || !r(i.shipmentId, 255) || typeof i.status != "string" || !e.has(i.status) || i.status === "CONFLICT" && i.shipmentId !== null || !(i.errorCode === null || typeof i.errorCode == "string" && t.has(i.errorCode))) return null;
22
+ a.push({
23
+ index: i.index,
24
+ externalEventId: i.externalEventId,
25
+ shipmentReference: i.shipmentReference,
26
+ shipmentId: i.shipmentId,
27
+ status: i.status,
28
+ errorCode: i.errorCode
29
+ });
30
+ }
31
+ return a;
32
+ }
33
+ //#endregion
34
+ export { i as trackingOutcomes };
@@ -0,0 +1,13 @@
1
+ import { TrackingQualityIntegrationsDocument, TrackingQualityDeliveriesDocument, TrackingQualityShipmentDocument } from '../../generated/tracking-quality-operations';
2
+
3
+ export { TrackingQualityIntegrationsDocument as QualityIntegrationsDocument, TrackingQualityDeliveriesDocument as QualityDeliveriesDocument, TrackingQualityShipmentDocument as QualityShipmentDocument, };
4
+ export interface TrackingUpdateOutcome {
5
+ index: number;
6
+ externalEventId: string | null;
7
+ shipmentReference: string;
8
+ shipmentId: string | null;
9
+ status: 'ACCEPTED' | 'DUPLICATE' | 'CONFLICT' | 'REJECTED';
10
+ errorCode: string | null;
11
+ }
12
+ /** JSON scalar decoding fails closed; legacy delivery history has no outcomes. */
13
+ export declare function trackingOutcomes(value: unknown): TrackingUpdateOutcome[] | null;
@@ -0,0 +1 @@
1
+ export default function TrackingEventQualityPage(): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { createLucideIcon as e } from "../createLucideIcon.js";
2
+ //#region node_modules/lucide-react/dist/esm/icons/radio-tower.js
3
+ var t = e("RadioTower", [
4
+ ["path", {
5
+ d: "M4.9 16.1C1 12.2 1 5.8 4.9 1.9",
6
+ key: "s0qx1y"
7
+ }],
8
+ ["path", {
9
+ d: "M7.8 4.7a6.14 6.14 0 0 0-.8 7.5",
10
+ key: "1idnkw"
11
+ }],
12
+ ["circle", {
13
+ cx: "12",
14
+ cy: "9",
15
+ r: "2",
16
+ key: "1092wv"
17
+ }],
18
+ ["path", {
19
+ d: "M16.2 4.8c2 2 2.26 5.11.8 7.47",
20
+ key: "ojru2q"
21
+ }],
22
+ ["path", {
23
+ d: "M19.1 1.9a9.96 9.96 0 0 1 0 14.1",
24
+ key: "rhi7fg"
25
+ }],
26
+ ["path", {
27
+ d: "M9.5 18h5",
28
+ key: "mfy3pd"
29
+ }],
30
+ ["path", {
31
+ d: "m8 22 4-11 4 11",
32
+ key: "25yftu"
33
+ }]
34
+ ]);
35
+ //#endregion
36
+ export { t as RadioTower };
@@ -0,0 +1,219 @@
1
+ import { RadioTower as e } from "../node_modules/lucide-react/dist/esm/icons/radio-tower.js";
2
+ import { useMoveTheWheels as t } from "../providers/MoveTheWheelsProvider.js";
3
+ import { Button as n } from "../components/ui.js";
4
+ import { PageLayout as r } from "../components/PageLayout.js";
5
+ import { useConditionQuery as i } from "../hooks/useContainerCondition.js";
6
+ import { ReuseState as a, reuseCard as o, reuseSelect as s } from "../components/ReuseControls.js";
7
+ import { TrackingQualityDeliveriesDocument as c, TrackingQualityIntegrationsDocument as l, TrackingQualityShipmentDocument as u } from "../src/generated/tracking-quality-operations.js";
8
+ import { trackingOutcomes as d } from "../hooks/useTrackingEventQuality.js";
9
+ import { useState as f } from "react";
10
+ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
11
+ //#region src/movethewheels/pages/TrackingEventQualityPage.tsx
12
+ var g = 20;
13
+ function _({ offset: e, length: t, busy: r, onChange: i }) {
14
+ return /* @__PURE__ */ h("div", {
15
+ className: "flex flex-wrap items-center gap-3",
16
+ children: [
17
+ /* @__PURE__ */ m(n, {
18
+ variant: "outline",
19
+ disabled: r || e === 0,
20
+ onClick: () => i(Math.max(0, e - g)),
21
+ children: "Previous page"
22
+ }),
23
+ /* @__PURE__ */ m("span", { children: t === void 0 ? "Page unavailable" : `${t} records on this page` }),
24
+ /* @__PURE__ */ m(n, {
25
+ variant: "outline",
26
+ disabled: r || t !== g || e >= 9980,
27
+ onClick: () => i(e + g),
28
+ children: "Next page"
29
+ })
30
+ ]
31
+ });
32
+ }
33
+ function v() {
34
+ let { workspaceId: n, tenantId: i, currentUser: a, apiGatewayUrl: o } = t();
35
+ return /* @__PURE__ */ m(r, {
36
+ title: "Tracking event quality",
37
+ subtitle: "Review delivery results before investigating a shipment.",
38
+ icon: /* @__PURE__ */ m(e, { "aria-hidden": "true" }),
39
+ children: /* @__PURE__ */ m(y, {}, `${n}:${i}:${a?.id}:${o}`)
40
+ });
41
+ }
42
+ function y() {
43
+ let [e, t] = f(0), [r, u] = f(""), [p, v] = f(0), [y, x] = f(0), [S, C] = f(null), w = i(l, { pagination: {
44
+ limit: g,
45
+ offset: e
46
+ } }, y), T = i(c, r ? {
47
+ id: r,
48
+ pagination: {
49
+ limit: g,
50
+ offset: p
51
+ }
52
+ } : null, y), E = w.data?.integrations, D = T.data?.integration?.events;
53
+ return /* @__PURE__ */ h("div", {
54
+ className: "space-y-6",
55
+ children: [
56
+ /* @__PURE__ */ m("p", { children: "Matching event keys and payloads create no additional tracking event. A changed payload with the same key is a conflict. Updates without a key can create new events." }),
57
+ /* @__PURE__ */ m("p", { children: "Each delivery admits at most five updates. An oversized or malformed delivery is rejected before updates are recorded. Delivery history can include older records without item results." }),
58
+ /* @__PURE__ */ h("section", {
59
+ className: o,
60
+ "aria-label": "Integration selection",
61
+ children: [
62
+ /* @__PURE__ */ m(a, {
63
+ loading: w.loading,
64
+ error: w.error
65
+ }),
66
+ E?.length === 0 && /* @__PURE__ */ m("p", { children: "No integrations on this page." }),
67
+ /* @__PURE__ */ h("label", {
68
+ className: "block",
69
+ children: ["Integration", /* @__PURE__ */ h("select", {
70
+ className: s,
71
+ value: r,
72
+ disabled: !E || w.loading,
73
+ onChange: (e) => {
74
+ u(e.target.value), v(0), C(null);
75
+ },
76
+ children: [/* @__PURE__ */ m("option", {
77
+ value: "",
78
+ children: "Choose an integration"
79
+ }), E?.map((e) => /* @__PURE__ */ h("option", {
80
+ value: e.id,
81
+ children: [
82
+ e.name,
83
+ " · ",
84
+ e.provider,
85
+ " · ",
86
+ e.status
87
+ ]
88
+ }, e.id))]
89
+ })]
90
+ }),
91
+ /* @__PURE__ */ m(_, {
92
+ offset: e,
93
+ length: E?.length,
94
+ busy: w.loading,
95
+ onChange: (e) => {
96
+ t(e), u(""), v(0), C(null);
97
+ }
98
+ })
99
+ ]
100
+ }),
101
+ r && /* @__PURE__ */ h("section", {
102
+ className: "space-y-4 rounded-xl border bg-bg-surface p-5",
103
+ "aria-label": "Tracking delivery history",
104
+ children: [
105
+ /* @__PURE__ */ h("div", {
106
+ className: "flex flex-wrap items-center justify-between gap-3",
107
+ children: [/* @__PURE__ */ m("h2", {
108
+ className: "text-lg font-semibold",
109
+ children: "Delivery history"
110
+ }), /* @__PURE__ */ m(n, {
111
+ variant: "outline",
112
+ disabled: T.loading,
113
+ onClick: () => x((e) => e + 1),
114
+ children: "Refresh results"
115
+ })]
116
+ }),
117
+ /* @__PURE__ */ m(a, {
118
+ loading: T.loading,
119
+ error: T.error
120
+ }),
121
+ T.data?.integration === null && /* @__PURE__ */ m("p", {
122
+ role: "status",
123
+ children: "Integration unavailable in this workspace."
124
+ }),
125
+ D?.length === 0 && /* @__PURE__ */ m("p", { children: "No deliveries on this page." }),
126
+ D?.map((e) => {
127
+ let t = d(e.outcomes);
128
+ return /* @__PURE__ */ h("article", {
129
+ className: "space-y-3 rounded-lg border p-4",
130
+ children: [/* @__PURE__ */ h("h3", {
131
+ className: "font-semibold",
132
+ children: [
133
+ e.status,
134
+ " · ",
135
+ new Date(e.receivedAt).toLocaleString()
136
+ ]
137
+ }), t === null ? /* @__PURE__ */ m("p", { children: "Per-update results unavailable for this delivery." }) : t.length === 0 ? /* @__PURE__ */ m("p", { children: "No normalized updates were attempted. This is not a count of raw sender items." }) : /* @__PURE__ */ m("ol", {
138
+ className: "space-y-3",
139
+ children: t.map((e) => /* @__PURE__ */ h("li", {
140
+ className: "break-words rounded-md bg-bg-surface p-3",
141
+ children: [
142
+ /* @__PURE__ */ h("p", { children: [
143
+ "Update ",
144
+ e.index + 1,
145
+ ": ",
146
+ /* @__PURE__ */ m("strong", { children: e.status })
147
+ ] }),
148
+ /* @__PURE__ */ h("p", { children: ["Event key: ", e.externalEventId ?? "Missing — not deduplicated"] }),
149
+ /* @__PURE__ */ h("p", { children: ["Shipment reference: ", e.shipmentReference || "Not recorded"] }),
150
+ e.errorCode && /* @__PURE__ */ h("p", { children: ["Reason: ", e.errorCode.replaceAll("_", " ").toLowerCase()] }),
151
+ e.status === "CONFLICT" && /* @__PURE__ */ m("p", { children: "The earlier receipt stays unchanged; this update adds no event." }),
152
+ e.shipmentId && /* @__PURE__ */ m(n, {
153
+ variant: "outline",
154
+ onClick: () => C(e.shipmentId),
155
+ children: "View shipment"
156
+ })
157
+ ]
158
+ }, e.index))
159
+ })]
160
+ }, e.id);
161
+ }),
162
+ /* @__PURE__ */ m(_, {
163
+ offset: p,
164
+ length: D?.length,
165
+ busy: T.loading,
166
+ onChange: v
167
+ })
168
+ ]
169
+ }),
170
+ S && /* @__PURE__ */ m(b, {
171
+ id: S,
172
+ onClose: () => C(null)
173
+ }, S),
174
+ /* @__PURE__ */ m("p", {
175
+ className: "text-sm text-text-secondary",
176
+ children: "These results describe recorded ingestion, not physical shipment truth. Ordering, late updates and provider corrections need separate policies."
177
+ })
178
+ ]
179
+ });
180
+ }
181
+ function b({ id: e, onClose: t }) {
182
+ let r = i(u, { id: e });
183
+ return /* @__PURE__ */ h("section", {
184
+ className: o,
185
+ "aria-label": "Shipment tracking summary",
186
+ children: [
187
+ /* @__PURE__ */ m("h2", {
188
+ className: "text-lg font-semibold",
189
+ children: "Shipment tracking summary"
190
+ }),
191
+ /* @__PURE__ */ m(n, {
192
+ variant: "outline",
193
+ onClick: t,
194
+ children: "Close shipment summary"
195
+ }),
196
+ /* @__PURE__ */ m(a, {
197
+ loading: r.loading,
198
+ error: r.error
199
+ }),
200
+ r.data?.shipment === null && /* @__PURE__ */ m("p", { children: "Shipment unavailable in this workspace." }),
201
+ r.data?.shipment && /* @__PURE__ */ h(p, { children: [
202
+ /* @__PURE__ */ h("p", { children: [
203
+ r.data.shipment.shipmentNumber,
204
+ " · ",
205
+ r.data.shipment.status
206
+ ] }),
207
+ /* @__PURE__ */ h("p", { children: ["Tracking number: ", r.data.shipment.trackingNumber ?? "Not recorded"] }),
208
+ /* @__PURE__ */ h("p", { children: [r.data.shipment.trackingEvents.length, " tracking events on the first page (up to 20)."] }),
209
+ /* @__PURE__ */ m("ul", { children: r.data.shipment.trackingEvents.map((e) => /* @__PURE__ */ h("li", { children: [
210
+ e.eventType,
211
+ " · ",
212
+ new Date(e.timestamp).toLocaleString()
213
+ ] }, e.id)) })
214
+ ] })
215
+ ]
216
+ });
217
+ }
218
+ //#endregion
219
+ export { v as default };
@@ -0,0 +1,44 @@
1
+ import { gql as e } from "@apollo/client";
2
+ //#region src/generated/tracking-quality-operations.ts
3
+ var t = e`
4
+ query TrackingQualityIntegrations($pagination: PaginationInput!) {
5
+ integrations(pagination: $pagination) {
6
+ id
7
+ name
8
+ provider
9
+ status
10
+ }
11
+ }
12
+ `, n = e`
13
+ query TrackingQualityDeliveries($id: ID!, $pagination: PaginationInput!) {
14
+ integration(id: $id) {
15
+ id
16
+ name
17
+ provider
18
+ status
19
+ events(pagination: $pagination) {
20
+ id
21
+ status
22
+ receivedAt
23
+ processedAt
24
+ outcomes
25
+ }
26
+ }
27
+ }
28
+ `, r = e`
29
+ query TrackingQualityShipment($id: ID!) {
30
+ shipment(id: $id) {
31
+ id
32
+ shipmentNumber
33
+ status
34
+ trackingNumber
35
+ trackingEvents(pagination: {limit: 20, offset: 0}) {
36
+ id
37
+ eventType
38
+ timestamp
39
+ }
40
+ }
41
+ }
42
+ `;
43
+ //#endregion
44
+ export { n as TrackingQualityDeliveriesDocument, t as TrackingQualityIntegrationsDocument, r as TrackingQualityShipmentDocument };
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-movethewheels",
3
- "version": "2026.1010.2",
3
+ "version": "2026.1011.1",
4
4
  "description": "Burdenoff MoveTheWheels Microfrontend - Logistics & Supply Chain Management Platform",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "src",
8
8
  "dist",
9
- "package.json"
9
+ "package.json",
10
+ "patches"
10
11
  ],
11
12
  "imports": {
12
13
  "#src/*": "./src/*",
@@ -34,7 +35,7 @@
34
35
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
35
36
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
36
37
  "validate:schema-drift": "graphql-schema-drift",
37
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema-drift && bun run lint:sanity && bun run format && bun run type:check && bun run verify:live-logistics-contract && bun run verify:empty-reuse-retry && bun run verify:empty-reuse-recovery && bun run verify:trip-utilization-input && bun run verify:trip-draft-context && bun run verify:stock-binding-input && bun run verify:stock-observation-input && bun run verify:pending-pick-input && bun run verify:freight-input && bun run verify:freight-recovery && bun run verify:procedure-input && bun run verify:consolidation && bun run verify:multimodal-single-attempt && bun run build && bun run build-storybook'",
38
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run validate:schema-drift && bun run lint:sanity && bun run format && bun run type:check && bun run verify:live-logistics-contract && bun run verify:empty-reuse-retry && bun run verify:empty-reuse-recovery && bun run verify:trip-utilization-input && bun run verify:trip-draft-context && bun run verify:stock-binding-input && bun run verify:stock-observation-input && bun run verify:pending-pick-input && bun run verify:freight-input && bun run verify:freight-recovery && bun run verify:procedure-input && bun run verify:consolidation && bun run verify:multimodal-single-attempt && bun run verify:tracking-quality-outcomes && bun run verify:braces-depth && bun run build && bun run build-storybook'",
38
39
  "storybook": "storybook dev -p 6006",
39
40
  "build-storybook": "storybook build",
40
41
  "test:storybook": "test-storybook",
@@ -42,7 +43,7 @@
42
43
  "schema:pull:introspect": "bash scripts/schema-pull.sh --mode introspect",
43
44
  "schema:check": "bash scripts/schema-pull.sh --check",
44
45
  "verify:stock-binding-input": "bun scripts/verify-stock-binding-input.mjs",
45
- "codegen": "graphql-codegen --project wspace && graphql-codegen --project container-foundation && graphql-codegen --project container-condition && graphql-codegen --project free-time && graphql-codegen --project empty-reuse && graphql-codegen --project trip-utilization && graphql-codegen --project stock-binding && graphql-codegen --project stock-observation && graphql-codegen --project pending-pick && graphql-codegen --project freight-cost && graphql-codegen --project logistics-procedure && graphql-codegen --project consolidation",
46
+ "codegen": "graphql-codegen --project wspace && graphql-codegen --project container-foundation && graphql-codegen --project container-condition && graphql-codegen --project free-time && graphql-codegen --project empty-reuse && graphql-codegen --project trip-utilization && graphql-codegen --project stock-binding && graphql-codegen --project stock-observation && graphql-codegen --project pending-pick && graphql-codegen --project freight-cost && graphql-codegen --project logistics-procedure && graphql-codegen --project consolidation && graphql-codegen --project tracking-quality",
46
47
  "codegen:all": "bun run schema:pull && bun run codegen",
47
48
  "switch:status": "bun scripts/deps-mode.ts status",
48
49
  "switch:remote": "bun scripts/deps-mode.ts remote",
@@ -59,7 +60,9 @@
59
60
  "verify:freight-input": "bun scripts/verify-freight-input.mjs",
60
61
  "verify:freight-recovery": "bun scripts/verify-freight-recovery-hook.mjs",
61
62
  "verify:procedure-input": "bun scripts/verify-procedure-input.mjs && bun scripts/verify-procedure-recovery-hook.mjs",
62
- "verify:consolidation": "bun scripts/verify-consolidation-input.mjs && bun scripts/verify-consolidation-recovery-hook.mjs && bun scripts/verify-consolidation-mounted-hook.mjs"
63
+ "verify:consolidation": "bun scripts/verify-consolidation-input.mjs && bun scripts/verify-consolidation-recovery-hook.mjs && bun scripts/verify-consolidation-mounted-hook.mjs",
64
+ "verify:tracking-quality-outcomes": "bun test scripts/verify-tracking-quality-outcomes.test.ts",
65
+ "verify:braces-depth": "bun test scripts/braces-depth-patch.test.ts"
63
66
  },
64
67
  "dependencies": {
65
68
  "@apollo/client": "^4.0.11",
@@ -114,7 +117,8 @@
114
117
  "typescript": "^5.9.0",
115
118
  "typescript-eslint": "^8.54.0",
116
119
  "vite": "8.0.16",
117
- "vite-plugin-dts": "3.9.1"
120
+ "vite-plugin-dts": "3.9.1",
121
+ "braces": "3.0.3"
118
122
  },
119
123
  "peerDependencies": {
120
124
  "@apollo/client": "^4.0.11",
@@ -140,6 +144,10 @@
140
144
  "yaml": ">=2.8.3",
141
145
  "picomatch": ">=2.3.2",
142
146
  "immutable": ">=3.8.3",
143
- "minimatch": ">=3.1.3"
147
+ "minimatch": ">=3.1.3",
148
+ "@graphql-tools/utils": "12.0.3"
149
+ },
150
+ "patchedDependencies": {
151
+ "braces@3.0.3": "patches/braces@3.0.3.patch"
144
152
  }
145
153
  }
@@ -0,0 +1,25 @@
1
+ # Reviewed build dependency mitigation
2
+
3
+ BOFF-9129 tracks the MoveTheWheels build dependency fixes. The canonical frozen
4
+ Bun installation applies `braces@3.0.3.patch` using `patchedDependencies`.
5
+
6
+ The upstream advisory [GHSA-vfj7-8cjw-p6xm](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm)
7
+ currently lists no fixed release. This local patch adds iterative validation before
8
+ recursive parse cleanup, compile, stringify and expand processing: nesting is bounded
9
+ to 128 and AST visits to 65,536, with malformed child and parent cycles rejected.
10
+ It preserves the tested ordinary escaped, quoted, range and nested glob patterns.
11
+
12
+ The version remains 3.0.3. Version-based dependency audits still report this HIGH
13
+ advisory; that result must remain visible with the pinned patch and regression evidence.
14
+ This is a reviewed nesting mitigation, not an upstream fixed-version or clean-audit claim.
15
+ It does not establish general limits on Cartesian expansion or every resource-exhaustion
16
+ pattern. npm installations do not apply Bun patches and must not claim this mitigation.
17
+
18
+ `bun run verify:braces-depth` exercises deeply nested balanced/unbalanced brace and
19
+ parenthesis strings, direct deep/cyclic/oversized ASTs and ordinary pattern semantics.
20
+ Normal `bun run sanity` includes those checks. The exact patch is shared with the
21
+ workspace public gateway (BOFF-9127); independent review covers its installed source.
22
+
23
+ The separate `@graphql-tools/utils` override uses published version 12.0.1 to remove
24
+ GHSA-7mx3-vvmw-hjmv from the actual Bun audit. Other inherited moderate findings remain
25
+ tracked under BOFF-8590. Keep all limitations attached to release evidence.
@@ -0,0 +1,87 @@
1
+ diff --git a/lib/compile.js b/lib/compile.js
2
+ --- a/lib/compile.js
3
+ +++ b/lib/compile.js
4
+ @@ -1,0 +2,2 @@
5
+ +
6
+ +const validateAst = require('./validate-ast');
7
+ @@ -6,0 +9 @@
8
+ + validateAst(ast);
9
+ diff --git a/lib/expand.js b/lib/expand.js
10
+ --- a/lib/expand.js
11
+ +++ b/lib/expand.js
12
+ @@ -1,0 +2,2 @@
13
+ +
14
+ +const validateAst = require('./validate-ast');
15
+ @@ -33,0 +36 @@
16
+ + validateAst(ast);
17
+ diff --git a/lib/parse.js b/lib/parse.js
18
+ --- a/lib/parse.js
19
+ +++ b/lib/parse.js
20
+ @@ -1,0 +2,2 @@
21
+ +
22
+ +const validateAst = require('./validate-ast');
23
+ @@ -146,0 +149 @@
24
+ + if (stack.length >= 128) throw new RangeError('Braces nesting limit exceeded');
25
+ @@ -212,0 +216 @@
26
+ + if (stack.length >= 128) throw new RangeError('Braces nesting limit exceeded');
27
+ @@ -327,0 +332 @@
28
+ + validateAst(ast);
29
+ diff --git a/lib/stringify.js b/lib/stringify.js
30
+ --- a/lib/stringify.js
31
+ +++ b/lib/stringify.js
32
+ @@ -1,0 +2,2 @@
33
+ +
34
+ +const validateAst = require('./validate-ast');
35
+ @@ -5,0 +8 @@
36
+ + validateAst(ast);
37
+ diff --git a/lib/validate-ast.js b/lib/validate-ast.js
38
+ new file mode 100644
39
+ --- /dev/null
40
+ +++ b/lib/validate-ast.js
41
+ @@ -0,0 +1,46 @@
42
+ +'use strict';
43
+ +
44
+ +// Local mitigation for GHSA-vfj7-8cjw-p6xm. Validate iteratively before any
45
+ +// recursive AST processing. Parent/prev links are metadata, not child edges.
46
+ +const MAX_DEPTH = 128;
47
+ +const MAX_NODES = 65536;
48
+ +
49
+ +module.exports = ast => {
50
+ + const pending = [{ node: ast, depth: 0, exit: false }];
51
+ + const active = new WeakSet();
52
+ + let visits = 0;
53
+ + while (pending.length > 0) {
54
+ + const item = pending.pop();
55
+ + const node = item.node;
56
+ + if (item.exit) {
57
+ + active.delete(node);
58
+ + continue;
59
+ + }
60
+ + if (node === null || typeof node !== 'object') throw new TypeError('Invalid braces AST');
61
+ + if (++visits > MAX_NODES) throw new RangeError('Braces AST node limit exceeded');
62
+ + if (item.depth > MAX_DEPTH) throw new RangeError('Braces nesting limit exceeded');
63
+ + if (active.has(node)) throw new RangeError('Cyclic braces AST');
64
+ + // expand() also follows parent links. Reject malformed parent chains before
65
+ + // its loop can hang; normal parser parents lead to the root.
66
+ + let parent = node.parent;
67
+ + const parents = new WeakSet();
68
+ + let parentDepth = 0;
69
+ + while (parent) {
70
+ + if (typeof parent !== 'object' || parents.has(parent) || ++parentDepth > MAX_DEPTH) {
71
+ + throw new RangeError('Invalid braces AST parent chain');
72
+ + }
73
+ + parents.add(parent);
74
+ + parent = parent.parent;
75
+ + }
76
+ + if (node.nodes !== undefined) {
77
+ + if (!Array.isArray(node.nodes)) throw new TypeError('Invalid braces AST children');
78
+ + if (node.nodes.length > MAX_NODES - visits) throw new RangeError('Braces AST node limit exceeded');
79
+ + active.add(node);
80
+ + pending.push({ node, depth: item.depth, exit: true });
81
+ + for (let index = node.nodes.length - 1; index >= 0; index--) {
82
+ + pending.push({ node: node.nodes[index], depth: item.depth + 1, exit: false });
83
+ + }
84
+ + if (pending.length > 2 * MAX_NODES) throw new RangeError('Braces AST node limit exceeded');
85
+ + }
86
+ + }
87
+ +};